diff options
author | 2017-06-12 19:09:04 -0700 | |
---|---|---|
committer | 2017-06-12 19:12:15 -0700 | |
commit | 8bf5d8cc184a639c4bc174e1c04541e88981f9f8 (patch) | |
tree | 6e891c3e30c5450d927fb425757264187d9a5870 /tensorflow | |
parent | 9f10f60fbd9fefaf225c1985014010b6b2f738c1 (diff) |
Add a kernel for a new TensorSummaryOpV2. Add a test for it.
Add a metadata field (type SummaryMetadata) to the Summary::Value proto.
Refactor the static methods in summary.py into a summary_op_util.py file.
Unlike the TensorSummaryOp, TensorSummaryOpV2
- passes a tag to the C++ level. This is more consistent with the rest of the summaries.
- lets the user pass a third argument that is a serialized SummaryMetadata proto. The SummaryMetadata contains plugin-specific data. This lets TensorBoard determine which events are relevant to which plugins.
We must make a new op instead of modifying the TensorSummaryOp because we must be backwards compatible.
PiperOrigin-RevId: 158797579
Diffstat (limited to 'tensorflow')
-rw-r--r-- | tensorflow/core/framework/summary.proto | 43 | ||||
-rw-r--r-- | tensorflow/core/kernels/BUILD | 1 | ||||
-rw-r--r-- | tensorflow/core/kernels/summary_tensor_op.cc | 48 | ||||
-rw-r--r-- | tensorflow/core/kernels/summary_tensor_op_test.cc | 101 | ||||
-rw-r--r-- | tensorflow/core/ops/logging_ops.cc | 22 | ||||
-rw-r--r-- | tensorflow/python/BUILD | 14 | ||||
-rw-r--r-- | tensorflow/python/ops/hidden_ops.txt | 1 | ||||
-rw-r--r-- | tensorflow/python/ops/summary_op_util.py | 107 | ||||
-rw-r--r-- | tensorflow/python/ops/summary_ops.py | 71 | ||||
-rw-r--r-- | tensorflow/python/summary/summary.py | 103 | ||||
-rw-r--r-- | tensorflow/tools/api/golden/tensorflow.-summary.-value.pbtxt | 4 | ||||
-rw-r--r-- | tensorflow/tools/api/golden/tensorflow.summary.-summary.-value.pbtxt | 4 |
12 files changed, 413 insertions, 106 deletions
diff --git a/tensorflow/core/framework/summary.proto b/tensorflow/core/framework/summary.proto index 3560b96dfc..12274d5e13 100644 --- a/tensorflow/core/framework/summary.proto +++ b/tensorflow/core/framework/summary.proto @@ -33,6 +33,26 @@ message HistogramProto { repeated double bucket = 7 [packed = true]; }; +// A SummaryMetadata encapsulates information on which plugins are able to make +// use of a certain summary value. +message SummaryMetadata { + message PluginData { + // The name of the plugin this data pertains to. + string plugin_name = 1; + + // The content to store for the plugin. The best practice is for this JSON + // string to be the canonical JSON serialization of a protocol buffer + // defined by the plugin. Converting that protobuf to and from JSON is the + // responsibility of the plugin code, and is not enforced by + // TensorFlow/TensorBoard. + string content = 2; + } + + // A list of plugin data. A single summary value instance may be used by more + // than 1 plugin. + repeated PluginData plugin_data = 1; +}; + // A Summary is a set of named values to be displayed by the // visualizer. // @@ -71,22 +91,21 @@ message Summary { } message Value { - // Name of the node that output this summary; in general, the name of a - // TensorSummary node. If the node in question has multiple outputs, then - // a ":\d+" suffix will be appended, like "some_op:13". - // Might not be set for legacy summaries (i.e. those not using the tensor - // value field) + // This field is deprecated and will not be set. string node_name = 7; - // Tag name for the data. Will only be used by legacy summaries - // (ie. those not using the tensor value field) - // For legacy summaries, will be used as the title of the graph - // in the visualizer. - // - // Tag is usually "op_name:value_name", where "op_name" itself can have - // structure to indicate grouping. + // Tag name for the data. Used by TensorBoard plugins to organize data. Tags + // are often organized by scope (which contains slashes to convey + // hierarchy). For example: foo/bar/0 string tag = 1; + // Contains metadata on the summary value such as which plugins may use it. + // Take note that many summary values may lack a metadata field. This is + // because the FileWriter only keeps a metadata object on the first summary + // value with a certain tag for each tag. TensorBoard then remembers which + // tags are associated with which plugins. This saves space. + SummaryMetadata metadata = 9; + // Value associated with the tag. oneof value { float simple_value = 2; diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 8ad345ec72..22a424f3ab 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2224,6 +2224,7 @@ tf_cc_tests( "summary_audio_op_test.cc", "summary_image_op_test.cc", "summary_op_test.cc", + "summary_tensor_op_test.cc", ], deps = [ ":logging", diff --git a/tensorflow/core/kernels/summary_tensor_op.cc b/tensorflow/core/kernels/summary_tensor_op.cc index e939a58794..c816974378 100644 --- a/tensorflow/core/kernels/summary_tensor_op.cc +++ b/tensorflow/core/kernels/summary_tensor_op.cc @@ -17,6 +17,7 @@ limitations under the License. #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/resource_mgr.h" #include "tensorflow/core/framework/summary.pb.h" +#include "tensorflow/core/framework/tensor.pb.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/protobuf.h" @@ -24,6 +25,53 @@ limitations under the License. namespace tensorflow { template <typename T> +class SummaryTensorOpV2 : public OpKernel { + public: + explicit SummaryTensorOpV2(OpKernelConstruction* context) + : OpKernel(context) {} + + void Compute(OpKernelContext* c) override { + const Tensor& tag = c->input(0); + OP_REQUIRES(c, IsLegacyScalar(tag.shape()), + errors::InvalidArgument("tag must be scalar")); + const Tensor& tensor = c->input(1); + const Tensor& serialized_summary_metadata_tensor = c->input(2); + + Summary s; + Summary::Value* v = s.add_value(); + v->set_tag(tag.scalar<string>()()); + + if (tensor.dtype() == DT_STRING) { + // tensor_util.makeNdarray doesn't work for strings in tensor_content + tensor.AsProtoField(v->mutable_tensor()); + } else { + tensor.AsProtoTensorContent(v->mutable_tensor()); + } + + v->mutable_metadata()->ParseFromString( + serialized_summary_metadata_tensor.scalar<string>()()); + + Tensor* summary_tensor = nullptr; + OP_REQUIRES_OK(c, c->allocate_output(0, TensorShape({}), &summary_tensor)); + CHECK(s.SerializeToString(&summary_tensor->scalar<string>()())); + } +}; + +#define REGISTER(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("TensorSummaryV2").Device(DEVICE_CPU).TypeConstraint<T>("T"), \ + SummaryTensorOpV2<T>); + +TF_CALL_ALL_TYPES(REGISTER) + +#undef REGISTER + +// NOTE(chizeng): We are phasing out the use of SummaryTensorOp in favor of +// SummaryTensorOpV2. This is because SummaryTensorOpV2 allows the callers to +// pass a tag (more consistent with other summaries) as well as serialized +// summary metadata used by plugins (which lets TensorBoard determine which +// events are relevant to which plugins). +template <typename T> class SummaryTensorOp : public OpKernel { public: explicit SummaryTensorOp(OpKernelConstruction* context) : OpKernel(context) {} diff --git a/tensorflow/core/kernels/summary_tensor_op_test.cc b/tensorflow/core/kernels/summary_tensor_op_test.cc new file mode 100644 index 0000000000..0006a71bd7 --- /dev/null +++ b/tensorflow/core/kernels/summary_tensor_op_test.cc @@ -0,0 +1,101 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include <functional> +#include <memory> + +#include "tensorflow/core/framework/allocator.h" +#include "tensorflow/core/framework/fake_input.h" +#include "tensorflow/core/framework/node_def_builder.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/summary.pb.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/framework/types.h" +#include "tensorflow/core/kernels/ops_testutil.h" +#include "tensorflow/core/kernels/ops_util.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/lib/histogram/histogram.h" +#include "tensorflow/core/lib/strings/strcat.h" +#include "tensorflow/core/platform/env.h" +#include "tensorflow/core/platform/logging.h" +#include "tensorflow/core/platform/protobuf.h" +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { +namespace { + +static void EXPECT_SummaryMatches(const Summary& actual, + const string& expected_str) { + Summary expected; + CHECK(protobuf::TextFormat::ParseFromString(expected_str, &expected)); + EXPECT_EQ(expected.DebugString(), actual.DebugString()); +} + +// -------------------------------------------------------------------------- +// SummaryTensorOpV2 +// -------------------------------------------------------------------------- +class SummaryTensorOpV2Test : public OpsTestBase { + protected: + void MakeOp() { + TF_ASSERT_OK(NodeDefBuilder("myop", "TensorSummaryV2") + .Input(FakeInput(DT_STRING)) + .Input(FakeInput(DT_STRING)) + .Input(FakeInput(DT_STRING)) + .Finalize(node_def())); + TF_ASSERT_OK(InitOp()); + } +}; + +TEST_F(SummaryTensorOpV2Test, BasicPluginData) { + MakeOp(); + + // Feed and run + AddInputFromArray<string>(TensorShape({}), {"tag_foo"}); + AddInputFromArray<string>(TensorShape({}), {"some string tensor content"}); + + // Create a SummaryMetadata that stores data for 2 plugins. + SummaryMetadata summary_metadata; + SummaryMetadata::PluginData* plugin_data_0 = + summary_metadata.add_plugin_data(); + plugin_data_0->set_plugin_name("foo"); + plugin_data_0->set_content("content_for_plugin_foo"); + SummaryMetadata::PluginData* plugin_data_1 = + summary_metadata.add_plugin_data(); + plugin_data_1->set_plugin_name("bar"); + plugin_data_1->set_content("content_for_plugin_bar"); + AddInputFromArray<string>(TensorShape({}), + {summary_metadata.SerializeAsString()}); + + TF_ASSERT_OK(RunOpKernel()); + + // Check the output size. + Tensor* out_tensor = GetOutput(0); + ASSERT_EQ(0, out_tensor->dims()); + Summary summary; + ParseProtoUnlimited(&summary, out_tensor->scalar<string>()()); + + ASSERT_EQ(1, summary.value_size()); + ASSERT_EQ("tag_foo", summary.value(0).tag()); + ASSERT_EQ(2, summary.value(0).metadata().plugin_data_size()); + ASSERT_EQ("foo", summary.value(0).metadata().plugin_data(0).plugin_name()); + ASSERT_EQ("content_for_plugin_foo", + summary.value(0).metadata().plugin_data(0).content()); + ASSERT_EQ("bar", summary.value(0).metadata().plugin_data(1).plugin_name()); + ASSERT_EQ("content_for_plugin_bar", + summary.value(0).metadata().plugin_data(1).content()); +} + +} // namespace +} // namespace tensorflow diff --git a/tensorflow/core/ops/logging_ops.cc b/tensorflow/core/ops/logging_ops.cc index 42bd12a5b3..4f5191f9f5 100644 --- a/tensorflow/core/ops/logging_ops.cc +++ b/tensorflow/core/ops/logging_ops.cc @@ -65,6 +65,24 @@ summarize: Only print this many entries of each tensor. // Operators that deal with SummaryProtos (encoded as DT_STRING tensors) as // inputs or outputs in various ways. +REGISTER_OP("TensorSummaryV2") + .Input("tag: string") + .Input("tensor: T") + // This serialized summary metadata field describes a summary value, + // specifically which plugins may use that summary. + .Input("serialized_summary_metadata: string") + .Output("summary: string") + .Attr("T: type") + .SetShapeFn(shape_inference::ScalarShape) + .Doc(R"doc( +Outputs a `Summary` protocol buffer with a tensor and per-plugin data. + +tag: A string attached to this summary. Used for organization in TensorBoard. +tensor: A tensor to serialize. +serialized_summary_metadata: A serialized SummaryMetadata proto. Contains plugin + data. +)doc"); + REGISTER_OP("TensorSummary") .Input("tensor: T") .Output("summary: string") @@ -76,6 +94,10 @@ REGISTER_OP("TensorSummary") .Doc(R"doc( Outputs a `Summary` protocol buffer with a tensor. +This op is being phased out in favor of TensorSummaryV2, which lets callers pass +a tag as well as a serialized SummaryMetadata proto string that contains +plugin-specific data. We will keep this op to maintain backwards compatibility. + tensor: A tensor to serialize. description: A json-encoded SummaryDescription proto. labels: An unused list of strings. diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index 8cb7c5d0df..2027eaf977 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -2028,6 +2028,7 @@ py_library( deps = [ ":framework_for_generated_wrappers", ":logging_ops_gen", + ":summary_op_util", ], ) @@ -3404,6 +3405,18 @@ tf_py_test( ) py_library( + name = "summary_op_util", + srcs = ["ops/summary_op_util.py"], + srcs_version = "PY2AND3", + visibility = ["//visibility:public"], + deps = [ + ":framework", + ":framework_for_generated_wrappers", + ":platform", + ], +) + +py_library( name = "summary", srcs = glob( ["summary/**/*.py"], @@ -3420,6 +3433,7 @@ py_library( ":platform", ":protos_all_py", ":pywrap_tensorflow", + ":summary_op_util", ":summary_ops", ":util", "//third_party/py/numpy", diff --git a/tensorflow/python/ops/hidden_ops.txt b/tensorflow/python/ops/hidden_ops.txt index 553e0dc135..9aef6bffde 100644 --- a/tensorflow/python/ops/hidden_ops.txt +++ b/tensorflow/python/ops/hidden_ops.txt @@ -219,6 +219,7 @@ MergeSummary Print ScalarSummary TensorSummary +TensorSummaryV2 # math_ops Abs diff --git a/tensorflow/python/ops/summary_op_util.py b/tensorflow/python/ops/summary_op_util.py new file mode 100644 index 0000000000..a3f6616902 --- /dev/null +++ b/tensorflow/python/ops/summary_op_util.py @@ -0,0 +1,107 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================== +"""Contains utility functions used by summary ops.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import contextlib +import re + +from tensorflow.python.framework import ops +from tensorflow.python.platform import tf_logging + + +def collect(val, collections, default_collections): + """Adds keys to a collection. + + Args: + val: The value to add per each key. + collections: A collection of keys to add. + default_collections: Used if collections is None. + """ + if collections is None: + collections = default_collections + for key in collections: + ops.add_to_collection(key, val) + + +_INVALID_TAG_CHARACTERS = re.compile(r'[^-/\w\.]') + + +def clean_tag(name): + """Cleans a tag. Removes illegal characters for instance. + + Args: + name: The original tag name to be processed. + + Returns: + The cleaned tag name. + """ + # In the past, the first argument to summary ops was a tag, which allowed + # arbitrary characters. Now we are changing the first argument to be the node + # name. This has a number of advantages (users of summary ops now can + # take advantage of the tf name scope system) but risks breaking existing + # usage, because a much smaller set of characters are allowed in node names. + # This function replaces all illegal characters with _s, and logs a warning. + # It also strips leading slashes from the name. + if name is not None: + new_name = _INVALID_TAG_CHARACTERS.sub('_', name) + new_name = new_name.lstrip('/') # Remove leading slashes + if new_name != name: + tf_logging.info('Summary name %s is illegal; using %s instead.' % + (name, new_name)) + name = new_name + return name + + +@contextlib.contextmanager +def summary_scope(name, family=None, default_name=None, values=None): + """Enters a scope used for the summary and yields both the name and tag. + + To ensure that the summary tag name is always unique, we create a name scope + based on `name` and use the full scope name in the tag. + + If `family` is set, then the tag name will be '<family>/<scope_name>', where + `scope_name` is `<outer_scope>/<family>/<name>`. This ensures that `family` + is always the prefix of the tag (and unmodified), while ensuring the scope + respects the outer scope from this this summary was created. + + Args: + name: A name for the generated summary node. + family: Optional; if provided, used as the prefix of the summary tag name. + default_name: Optional; if provided, used as default name of the summary. + values: Optional; passed as `values` parameter to name_scope. + + Yields: + A tuple `(tag, scope)`, both of which are unique and should be used for the + tag and the scope for the summary to output. + """ + name = clean_tag(name) + family = clean_tag(family) + # Use family name in the scope to ensure uniqueness of scope/tag. + scope_base_name = name if family is None else '{}/{}'.format(family, name) + with ops.name_scope(scope_base_name, default_name, values=values) as scope: + if family is None: + tag = scope.rstrip('/') + else: + # Prefix our scope with family again so it displays in the right tab. + tag = '{}/{}'.format(family, scope.rstrip('/')) + # Note: tag is not 100% unique if the user explicitly enters a scope with + # the same name as family, then later enter it again before summaries. + # This is very contrived though, and we opt here to let it be a runtime + # exception if tags do indeed collide. + yield (tag, scope) diff --git a/tensorflow/python/ops/summary_ops.py b/tensorflow/python/ops/summary_ops.py index 005f17dc45..4ad0862dcc 100644 --- a/tensorflow/python/ops/summary_ops.py +++ b/tensorflow/python/ops/summary_ops.py @@ -22,19 +22,13 @@ from google.protobuf import json_format from tensorflow.core.framework import summary_pb2 from tensorflow.python.framework import ops from tensorflow.python.ops import gen_logging_ops +from tensorflow.python.ops import summary_op_util # go/tf-wildcard-import # pylint: disable=wildcard-import from tensorflow.python.ops.gen_logging_ops import * # pylint: enable=wildcard-import -def _Collect(val, collections, default_collections): - if collections is None: - collections = default_collections - for key in collections: - ops.add_to_collection(key, val) - - # TODO(dandelion): As currently implemented, this op has several problems. # The 'summary_description' field is passed but not used by the kernel. # The 'name' field is used to creat a scope and passed down via name=scope, @@ -78,8 +72,67 @@ def tensor_summary( # pylint: disable=invalid-name tensor=tensor, description=description, name=scope) - _Collect(val, collections, [ops.GraphKeys.SUMMARIES]) + summary_op_util.collect(val, collections, [ops.GraphKeys.SUMMARIES]) return val - ops.NotDifferentiable("TensorSummary") + + +def _tensor_summary_v2( # pylint: disable=invalid-name + name, + tensor, + summary_description=None, + collections=None, + summary_metadata=None, + family=None): + # pylint: disable=line-too-long + """Outputs a `Summary` protocol buffer with a serialized tensor.proto. + + NOTE(chizeng): This method is temporary. It should never make it into + TensorFlow 1.3, and nothing should depend on it. This method should be deleted + before August 2017 (ideally, earlier). This method exists to unblock the + TensorBoard plugin refactoring effort. We will later modify the tensor_summary + method to directly make use of the TensorSummaryV2 op. There must be a 3-week + difference between adding a new op (C++) and changing a python interface to + use it. + + The generated + [`Summary`](https://www.tensorflow.org/code/tensorflow/core/framework/summary.proto) + has one summary value containing the input tensor. + + Args: + name: A name for the generated node. Will also serve as the series name in + TensorBoard. + tensor: A tensor of any type and shape to serialize. + summary_description: This is currently un-used but must be kept for + backwards compatibility. + collections: Optional list of graph collections keys. The new summary op is + added to these collections. Defaults to `[GraphKeys.SUMMARIES]`. + summary_metadata: Optional SummaryMetadata proto (which describes which + plugins may use the summary value). + family: Optional; if provided, used as the prefix of the summary tag name, + which controls the tab name used for display on Tensorboard. + + Returns: + A scalar `Tensor` of type `string`. The serialized `Summary` protocol + buffer. + """ + # pylint: enable=line-too-long + + # The summary description is unused now. + del summary_description + + serialized_summary_metadata = "" + if summary_metadata: + serialized_summary_metadata = summary_metadata.SerializeToString() + + with summary_op_util.summary_scope( + name, family, values=[tensor]) as (tag, scope): + val = gen_logging_ops._tensor_summary_v2( + tensor=tensor, + tag=tag, + description="", + name=scope, + serialized_summary_metadata=serialized_summary_metadata) + summary_op_util.collect(val, collections, [ops.GraphKeys.SUMMARIES]) + return val diff --git a/tensorflow/python/summary/summary.py b/tensorflow/python/summary/summary.py index cb8778be28..7ff01a51f3 100644 --- a/tensorflow/python/summary/summary.py +++ b/tensorflow/python/summary/summary.py @@ -36,9 +36,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import contextlib as _contextlib -import re as _re - from google.protobuf import json_format as _json_format # exports Summary, SummaryDescription, Event, TaggedRunMetadata, SessionLog @@ -53,14 +50,14 @@ from tensorflow.core.util.event_pb2 import TaggedRunMetadata from tensorflow.python.framework import dtypes as _dtypes from tensorflow.python.framework import ops as _ops from tensorflow.python.ops import gen_logging_ops as _gen_logging_ops +from tensorflow.python.ops import summary_op_util as _summary_op_util -# exports tensor_summary +# exports tensor-related summaries # pylint: disable=unused-import +from tensorflow.python.ops.summary_ops import _tensor_summary_v2 from tensorflow.python.ops.summary_ops import tensor_summary # pylint: enable=unused-import -from tensorflow.python.platform import tf_logging as _logging - # exports text # pylint: disable=unused-import from tensorflow.python.summary.text_summary import text_summary as text @@ -76,74 +73,6 @@ from tensorflow.python.util import compat as _compat from tensorflow.python.util.all_util import remove_undocumented -def _collect(val, collections, default_collections): - if collections is None: - collections = default_collections - for key in collections: - _ops.add_to_collection(key, val) - - -_INVALID_TAG_CHARACTERS = _re.compile(r'[^-/\w\.]') - - -def _clean_tag(name): - # In the past, the first argument to summary ops was a tag, which allowed - # arbitrary characters. Now we are changing the first argument to be the node - # name. This has a number of advantages (users of summary ops now can - # take advantage of the tf name scope system) but risks breaking existing - # usage, because a much smaller set of characters are allowed in node names. - # This function replaces all illegal characters with _s, and logs a warning. - # It also strips leading slashes from the name. - if name is not None: - new_name = _INVALID_TAG_CHARACTERS.sub('_', name) - new_name = new_name.lstrip('/') # Remove leading slashes - if new_name != name: - _logging.info( - 'Summary name %s is illegal; using %s instead.' % - (name, new_name)) - name = new_name - return name - - -@_contextlib.contextmanager -def _summary_scope(name, family=None, default_name=None, values=None): - """Enters a scope used for the summary and yields both the name and tag. - - To ensure that the summary tag name is always unique, we create a name scope - based on `name` and use the full scope name in the tag. - - If `family` is set, then the tag name will be '<family>/<scope_name>', where - `scope_name` is `<outer_scope>/<family>/<name>`. This ensures that `family` - is always the prefix of the tag (and unmodified), while ensuring the scope - respects the outer scope from this this summary was created. - - Args: - name: A name for the generated summary node. - family: Optional; if provided, used as the prefix of the summary tag name. - default_name: Optional; if provided, used as default name of the summary. - values: Optional; passed as `values` parameter to name_scope. - - Yields: - A tuple `(tag, scope)`, both of which are unique and should be used for the - tag and the scope for the summary to output. - """ - name = _clean_tag(name) - family = _clean_tag(family) - # Use family name in the scope to ensure uniqueness of scope/tag. - scope_base_name = name if family is None else '{}/{}'.format(family, name) - with _ops.name_scope(scope_base_name, default_name, values=values) as scope: - if family is None: - tag = scope.rstrip('/') - else: - # Prefix our scope with family again so it displays in the right tab. - tag = '{}/{}'.format(family, scope.rstrip('/')) - # Note: tag is not 100% unique if the user explicitly enters a scope with - # the same name as family, then later enter it again before summaries. - # This is very contrived though, and we opt here to let it be a runtime - # exception if tags do indeed collide. - yield (tag, scope) - - def scalar(name, tensor, collections=None, family=None): """Outputs a `Summary` protocol buffer containing a single scalar value. @@ -164,10 +93,11 @@ def scalar(name, tensor, collections=None, family=None): Raises: ValueError: If tensor has the wrong shape or type. """ - with _summary_scope(name, family, values=[tensor]) as (tag, scope): + with _summary_op_util.summary_scope( + name, family, values=[tensor]) as (tag, scope): # pylint: disable=protected-access val = _gen_logging_ops._scalar_summary(tags=tag, values=tensor, name=scope) - _collect(val, collections, [_ops.GraphKeys.SUMMARIES]) + _summary_op_util.collect(val, collections, [_ops.GraphKeys.SUMMARIES]) return val @@ -216,11 +146,12 @@ def image(name, tensor, max_outputs=3, collections=None, family=None): A scalar `Tensor` of type `string`. The serialized `Summary` protocol buffer. """ - with _summary_scope(name, family, values=[tensor]) as (tag, scope): + with _summary_op_util.summary_scope( + name, family, values=[tensor]) as (tag, scope): # pylint: disable=protected-access val = _gen_logging_ops._image_summary( tag=tag, tensor=tensor, max_images=max_outputs, name=scope) - _collect(val, collections, [_ops.GraphKeys.SUMMARIES]) + _summary_op_util.collect(val, collections, [_ops.GraphKeys.SUMMARIES]) return val @@ -253,12 +184,13 @@ def histogram(name, values, collections=None, family=None): A scalar `Tensor` of type `string`. The serialized `Summary` protocol buffer. """ - with _summary_scope(name, family, values=[values], - default_name='HistogramSummary') as (tag, scope): + with _summary_op_util.summary_scope( + name, family, values=[values], + default_name='HistogramSummary') as (tag, scope): # pylint: disable=protected-access val = _gen_logging_ops._histogram_summary( tag=tag, values=values, name=scope) - _collect(val, collections, [_ops.GraphKeys.SUMMARIES]) + _summary_op_util.collect(val, collections, [_ops.GraphKeys.SUMMARIES]) return val @@ -297,14 +229,15 @@ def audio(name, tensor, sample_rate, max_outputs=3, collections=None, A scalar `Tensor` of type `string`. The serialized `Summary` protocol buffer. """ - with _summary_scope(name, family=family, values=[tensor]) as (tag, scope): + with _summary_op_util.summary_scope( + name, family=family, values=[tensor]) as (tag, scope): # pylint: disable=protected-access sample_rate = _ops.convert_to_tensor( sample_rate, dtype=_dtypes.float32, name='sample_rate') val = _gen_logging_ops._audio_summary_v2( tag=tag, tensor=tensor, max_outputs=max_outputs, sample_rate=sample_rate, name=scope) - _collect(val, collections, [_ops.GraphKeys.SUMMARIES]) + _summary_op_util.collect(val, collections, [_ops.GraphKeys.SUMMARIES]) return val @@ -332,11 +265,11 @@ def merge(inputs, collections=None, name=None): buffer resulting from the merging. """ # pylint: enable=line-too-long - name = _clean_tag(name) + name = _summary_op_util.clean_tag(name) with _ops.name_scope(name, 'Merge', inputs): # pylint: disable=protected-access val = _gen_logging_ops._merge_summary(inputs=inputs, name=name) - _collect(val, collections, []) + _summary_op_util.collect(val, collections, []) return val diff --git a/tensorflow/tools/api/golden/tensorflow.-summary.-value.pbtxt b/tensorflow/tools/api/golden/tensorflow.-summary.-value.pbtxt index d02fb9ecd4..ffb4f45fc5 100644 --- a/tensorflow/tools/api/golden/tensorflow.-summary.-value.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.-summary.-value.pbtxt @@ -23,6 +23,10 @@ tf_class { mtype: "<type \'int\'>" } member { + name: "METADATA_FIELD_NUMBER" + mtype: "<type \'int\'>" + } + member { name: "NODE_NAME_FIELD_NUMBER" mtype: "<type \'int\'>" } diff --git a/tensorflow/tools/api/golden/tensorflow.summary.-summary.-value.pbtxt b/tensorflow/tools/api/golden/tensorflow.summary.-summary.-value.pbtxt index 5294b37f57..b319cd03d9 100644 --- a/tensorflow/tools/api/golden/tensorflow.summary.-summary.-value.pbtxt +++ b/tensorflow/tools/api/golden/tensorflow.summary.-summary.-value.pbtxt @@ -23,6 +23,10 @@ tf_class { mtype: "<type \'int\'>" } member { + name: "METADATA_FIELD_NUMBER" + mtype: "<type \'int\'>" + } + member { name: "NODE_NAME_FIELD_NUMBER" mtype: "<type \'int\'>" } |