aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow
diff options
context:
space:
mode:
authorGravatar A. Unique TensorFlower <gardener@tensorflow.org>2017-06-12 19:09:04 -0700
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2017-06-12 19:12:15 -0700
commit8bf5d8cc184a639c4bc174e1c04541e88981f9f8 (patch)
tree6e891c3e30c5450d927fb425757264187d9a5870 /tensorflow
parent9f10f60fbd9fefaf225c1985014010b6b2f738c1 (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.proto43
-rw-r--r--tensorflow/core/kernels/BUILD1
-rw-r--r--tensorflow/core/kernels/summary_tensor_op.cc48
-rw-r--r--tensorflow/core/kernels/summary_tensor_op_test.cc101
-rw-r--r--tensorflow/core/ops/logging_ops.cc22
-rw-r--r--tensorflow/python/BUILD14
-rw-r--r--tensorflow/python/ops/hidden_ops.txt1
-rw-r--r--tensorflow/python/ops/summary_op_util.py107
-rw-r--r--tensorflow/python/ops/summary_ops.py71
-rw-r--r--tensorflow/python/summary/summary.py103
-rw-r--r--tensorflow/tools/api/golden/tensorflow.-summary.-value.pbtxt4
-rw-r--r--tensorflow/tools/api/golden/tensorflow.summary.-summary.-value.pbtxt4
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\'>"
}