aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/core/example
diff options
context:
space:
mode:
authorGravatar A. Unique TensorFlower <nobody@tensorflow.org>2016-05-18 08:04:37 -0800
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2016-05-18 09:11:05 -0700
commit60adf6fe001ebef450120ce0b81412b666c02649 (patch)
tree4b61e41627ace3aaa567985fa383e9e34dd496fa /tensorflow/core/example
parent91615e7667b6e3c21c8c61f5accaf4cba89b3b06 (diff)
Add ExtractExampleParserConfiguration method
- Extract the fixed length and variable length feature configurations output tensor names from a given GraphDef. - This will allow for the use case of bypassing an unnecessary tensorflow.Example serialize/deserialize at serving/inference time by extracting the configuration, running the proto -> tensor helpers directly and feeding the graph with the properly named tensors Change: 122636456
Diffstat (limited to 'tensorflow/core/example')
-rw-r--r--tensorflow/core/example/example_parser_configuration.cc160
-rw-r--r--tensorflow/core/example/example_parser_configuration.h47
-rw-r--r--tensorflow/core/example/example_parser_configuration_test.cc148
-rw-r--r--tensorflow/core/example/testdata/parse_example_graph_def.pbtxt334
4 files changed, 689 insertions, 0 deletions
diff --git a/tensorflow/core/example/example_parser_configuration.cc b/tensorflow/core/example/example_parser_configuration.cc
new file mode 100644
index 0000000000..798ebfd364
--- /dev/null
+++ b/tensorflow/core/example/example_parser_configuration.cc
@@ -0,0 +1,160 @@
+/* Copyright 2016 Google Inc. 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 "tensorflow/core/example/example_parser_configuration.h"
+
+#include <vector>
+
+#include "tensorflow/core/example/example.pb.h"
+#include "tensorflow/core/example/feature.pb_text.h"
+#include "tensorflow/core/framework/numeric_op.h"
+#include "tensorflow/core/framework/register_types.h"
+#include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/lib/strings/strcat.h"
+#include "tensorflow/core/platform/logging.h"
+#include "tensorflow/core/platform/protobuf.h"
+
+namespace tensorflow {
+
+Status FindNodeIndexByName(const tensorflow::GraphDef& graph,
+ const string& node_name, int* node_idx) {
+ for (int i = 0; i < graph.node_size(); ++i) {
+ const auto& node = graph.node(i);
+ if (node.name() == node_name) {
+ *node_idx = i;
+ return Status::OK();
+ }
+ }
+ return errors::InvalidArgument(node_name, " not found in GraphDef");
+}
+
+Status ExtractExampleParserConfiguration(
+ const tensorflow::GraphDef& graph, const string& node_name,
+ tensorflow::Session* session,
+ std::vector<FixedLenFeature>* fixed_len_features,
+ std::vector<VarLenFeature>* var_len_features) {
+ int node_idx;
+ TF_RETURN_IF_ERROR(FindNodeIndexByName(graph, node_name, &node_idx));
+
+ const auto& node = graph.node(node_idx);
+ if (node.op() != "ParseExample") {
+ return errors::InvalidArgument(node_name, " node is not a ParseExample op");
+ }
+
+ auto& attr_map = node.attr();
+ auto num_sparse = attr_map.at("Nsparse").i();
+ auto num_dense = attr_map.at("Ndense").i();
+ fixed_len_features->resize(num_dense);
+ var_len_features->resize(num_sparse);
+
+ auto tdense = attr_map.at("Tdense");
+ auto dense_shapes = attr_map.at("dense_shapes");
+ auto sparse_types = attr_map.at("sparse_types");
+
+ // Consistency check attributes.
+ if (tdense.list().type_size() != num_dense) {
+ return errors::InvalidArgument("Node attr Tdense has ",
+ tdense.list().type_size(),
+ " elements != Ndense attr: ", num_dense);
+ }
+
+ if (dense_shapes.list().shape_size() != num_dense) {
+ return errors::InvalidArgument("Node attr dense_shapes has ",
+ dense_shapes.list().shape_size(),
+ " elements != Ndense attr: ", num_dense);
+ }
+
+ if (sparse_types.list().type_size() != num_sparse) {
+ return errors::InvalidArgument("Node attr sparse_types has ",
+ sparse_types.list().type_size(),
+ " elements != NSparse attr: ", num_sparse);
+ }
+
+ for (int i = 0; i < tdense.list().type_size(); ++i) {
+ (*fixed_len_features)[i].dtype = tdense.list().type(i);
+ // Convert TensorShapeProto to TensorShape.
+ (*fixed_len_features)[i].shape = TensorShape(dense_shapes.list().shape(i));
+ }
+
+ for (int i = 0; i < sparse_types.list().type_size(); ++i) {
+ (*var_len_features)[i].dtype = sparse_types.list().type(i);
+ }
+
+ // We must fetch the configuration input tensors to the ParseExample op.
+ // Skipping index = 0, which is the serialized proto input.
+ std::vector<string> fetch_names(node.input_size() - 1);
+ for (int i = 1; i < node.input_size(); ++i) {
+ fetch_names[i - 1] = node.input(i);
+ }
+
+ std::vector<Tensor> op_input_tensors;
+
+ TF_RETURN_IF_ERROR(session->Run({}, // no_inputs,
+ fetch_names, {}, // no target_node_names,
+ &op_input_tensors));
+
+ // The input tensors are laid out sequentially in a flat manner.
+ // Here are the various start offsets.
+ int sparse_keys_start = 1;
+ int dense_keys_start = sparse_keys_start + num_sparse;
+ int dense_defaults_start = dense_keys_start + num_dense;
+
+ for (int i = 0; i < num_sparse; ++i) {
+ int input_idx = sparse_keys_start + i;
+ (*var_len_features)[i].key = op_input_tensors[input_idx].scalar<string>()();
+ }
+
+ for (int i = 0; i < num_dense; ++i) {
+ FixedLenFeature& config = (*fixed_len_features)[i];
+ int dense_keys_offset = dense_keys_start + i;
+ config.key = op_input_tensors[dense_keys_offset].scalar<string>()();
+
+ int defaults_offset = dense_defaults_start + i;
+ config.default_value = op_input_tensors[defaults_offset];
+ }
+
+ // The output tensors are laid out sequentially in a flat manner.
+ // Here are the various start offsets.
+ int sparse_indices_output_start = 0;
+ int sparse_values_output_start = sparse_indices_output_start + num_sparse;
+ int sparse_shapes_output_start = sparse_values_output_start + num_sparse;
+ int dense_values_output_start = sparse_shapes_output_start + num_sparse;
+
+ string node_output_prefix = strings::StrCat(node_name, ":");
+
+ for (int i = 0; i < num_sparse; ++i) {
+ VarLenFeature& config = (*var_len_features)[i];
+
+ int indices_offset = sparse_indices_output_start + i;
+ config.indices_output_tensor_name =
+ strings::StrCat(node_output_prefix, indices_offset);
+
+ int values_offset = sparse_values_output_start + i;
+ config.values_output_tensor_name =
+ strings::StrCat(node_output_prefix, values_offset);
+
+ int shapes_offset = sparse_shapes_output_start + i;
+ config.shapes_output_tensor_name =
+ strings::StrCat(node_output_prefix, shapes_offset);
+ }
+
+ for (int i = 0; i < num_dense; ++i) {
+ int output_idx = dense_values_output_start + i;
+ (*fixed_len_features)[i].values_output_tensor_name =
+ strings::StrCat(node_output_prefix, output_idx);
+ }
+ return Status::OK();
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/example/example_parser_configuration.h b/tensorflow/core/example/example_parser_configuration.h
new file mode 100644
index 0000000000..cf90dd7f4b
--- /dev/null
+++ b/tensorflow/core/example/example_parser_configuration.h
@@ -0,0 +1,47 @@
+/* Copyright 2016 Google Inc. 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.
+==============================================================================*/
+
+#ifndef THIRD_PARTY_TENSORFLOW_CORE_EXAMPLE_EXAMPLE_PARSER_CONFIGURATION_H_
+#define THIRD_PARTY_TENSORFLOW_CORE_EXAMPLE_EXAMPLE_PARSER_CONFIGURATION_H_
+
+#include <string>
+#include <vector>
+
+#include "tensorflow/core/example/example.pb.h"
+#include "tensorflow/core/framework/allocator.h"
+#include "tensorflow/core/framework/graph.pb.h"
+#include "tensorflow/core/framework/tensor.h"
+#include "tensorflow/core/framework/types.h"
+#include "tensorflow/core/platform/types.h"
+#include "tensorflow/core/public/session.h"
+#include "tensorflow/core/util/example_proto_helper.h"
+#include "tensorflow/core/util/sparse/sparse_tensor.h"
+
+// This is a set of helper methods that will make it possible to share
+// tensorflow::Example proto Tensor conversion code inside the ExampleParserOp
+// OpKernel as well as in external code.
+namespace tensorflow {
+
+// Given a graph and the node_name of a ParseExample op,
+// extract the FixedLenFeature/VarLenFeature configurations.
+Status ExtractExampleParserConfiguration(
+ const tensorflow::GraphDef& graph, const string& node_name,
+ tensorflow::Session* session,
+ std::vector<FixedLenFeature>* fixed_len_features,
+ std::vector<VarLenFeature>* var_len_features);
+
+} // namespace tensorflow
+
+#endif // THIRD_PARTY_TENSORFLOW_CORE_EXAMPLE_EXAMPLE_PARSE_CONFIGURATION_H_
diff --git a/tensorflow/core/example/example_parser_configuration_test.cc b/tensorflow/core/example/example_parser_configuration_test.cc
new file mode 100644
index 0000000000..e804b123f2
--- /dev/null
+++ b/tensorflow/core/example/example_parser_configuration_test.cc
@@ -0,0 +1,148 @@
+/* Copyright 2016 Google Inc. 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 "tensorflow/core/example/example_parser_configuration.h"
+
+#include "tensorflow/core/example/example.pb.h"
+#include "tensorflow/core/lib/io/path.h"
+#include "tensorflow/core/platform/protobuf.h"
+#include "tensorflow/core/platform/test.h"
+#include "tensorflow/core/public/session_options.h"
+#include "tensorflow/core/util/example_proto_helper.h"
+
+namespace tensorflow {
+namespace {
+
+void ReadFileToStringOrDie(Env* env, const string& filename, string* output) {
+ TF_CHECK_OK(ReadFileToString(env, filename, output));
+}
+Session* CreateSession() {
+ SessionOptions options;
+ (*options.config.mutable_device_count())["CPU"] = 2;
+ return NewSession(options);
+}
+
+class ExtractExampleParserConfigurationTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ string proto_string;
+ string filename =
+ io::JoinPath(testing::TensorFlowSrcRoot(),
+ "core/example/testdata/parse_example_graph_def.pbtxt");
+ ReadFileToStringOrDie(Env::Default(), filename, &proto_string);
+ protobuf::TextFormat::ParseFromString(proto_string, &graph_def_);
+ session_.reset(CreateSession());
+ session_->Create(graph_def_);
+ }
+
+ NodeDef* parse_example_node() {
+ for (int i = 0; i < graph_def_.node_size(); ++i) {
+ auto mutable_node = graph_def_.mutable_node(i);
+ if (mutable_node->name() == "ParseExample/ParseExample") {
+ return mutable_node;
+ }
+ }
+ return nullptr;
+ }
+ GraphDef graph_def_;
+ std::unique_ptr<Session> session_;
+};
+
+TEST_F(ExtractExampleParserConfigurationTest, OpNotFound) {
+ std::vector<FixedLenFeature> dense_vec;
+ std::vector<VarLenFeature> sparse_vec;
+ Status status = ExtractExampleParserConfiguration(
+ graph_def_, "BlarseExample/ParseExample", session_.get(), &dense_vec,
+ &sparse_vec);
+
+ EXPECT_EQ(status.code(), error::INVALID_ARGUMENT);
+}
+
+TEST_F(ExtractExampleParserConfigurationTest, InconsistentAttrNsparse) {
+ std::vector<FixedLenFeature> dense_vec;
+ std::vector<VarLenFeature> sparse_vec;
+
+ NodeDef* node = parse_example_node();
+ auto mutable_attr = node->mutable_attr();
+ (*mutable_attr)["Nsparse"].set_i(3);
+
+ Status status = ExtractExampleParserConfiguration(
+ graph_def_, "ParseExample/ParseExample", session_.get(), &dense_vec,
+ &sparse_vec);
+
+ EXPECT_EQ(status.code(), error::INVALID_ARGUMENT);
+}
+
+TEST_F(ExtractExampleParserConfigurationTest, InconsistentAttrNdense) {
+ std::vector<FixedLenFeature> dense_vec;
+ std::vector<VarLenFeature> sparse_vec;
+
+ NodeDef* node = parse_example_node();
+ auto mutable_attr = node->mutable_attr();
+ (*mutable_attr)["Ndense"].set_i(2);
+
+ Status status = ExtractExampleParserConfiguration(
+ graph_def_, "ParseExample/ParseExample", session_.get(), &dense_vec,
+ &sparse_vec);
+
+ EXPECT_EQ(status.code(), error::INVALID_ARGUMENT);
+}
+
+TEST_F(ExtractExampleParserConfigurationTest, Basic) {
+ std::vector<FixedLenFeature> dense_vec;
+ std::vector<VarLenFeature> sparse_vec;
+ Status status = ExtractExampleParserConfiguration(
+ graph_def_, "ParseExample/ParseExample", session_.get(), &dense_vec,
+ &sparse_vec);
+
+ EXPECT_EQ(Status::OK(), status);
+ EXPECT_EQ(2, sparse_vec.size());
+ EXPECT_EQ(3, dense_vec.size());
+
+ EXPECT_EQ("sf0", sparse_vec[0].key);
+ EXPECT_EQ(DT_STRING, sparse_vec[0].dtype);
+ EXPECT_EQ("ParseExample/ParseExample:0",
+ sparse_vec[0].indices_output_tensor_name);
+ EXPECT_EQ("ParseExample/ParseExample:2",
+ sparse_vec[0].values_output_tensor_name);
+ EXPECT_EQ("ParseExample/ParseExample:4",
+ sparse_vec[0].shapes_output_tensor_name);
+
+ EXPECT_EQ("sf1", sparse_vec[1].key);
+ EXPECT_EQ(DT_STRING, sparse_vec[1].dtype);
+ EXPECT_EQ("ParseExample/ParseExample:1",
+ sparse_vec[1].indices_output_tensor_name);
+ EXPECT_EQ("ParseExample/ParseExample:3",
+ sparse_vec[1].values_output_tensor_name);
+ EXPECT_EQ("ParseExample/ParseExample:5",
+ sparse_vec[1].shapes_output_tensor_name);
+
+ EXPECT_EQ("x", dense_vec[0].key);
+ EXPECT_EQ(DT_FLOAT, dense_vec[0].dtype);
+ EXPECT_EQ("ParseExample/ParseExample:6",
+ dense_vec[0].values_output_tensor_name);
+
+ EXPECT_EQ("y", dense_vec[1].key);
+ EXPECT_EQ(DT_FLOAT, dense_vec[1].dtype);
+ EXPECT_EQ("ParseExample/ParseExample:7",
+ dense_vec[1].values_output_tensor_name);
+
+ EXPECT_EQ("z", dense_vec[2].key);
+ EXPECT_EQ(DT_FLOAT, dense_vec[2].dtype);
+ EXPECT_EQ("ParseExample/ParseExample:8",
+ dense_vec[2].values_output_tensor_name);
+}
+
+} // namespace
+} // namespace tensorflow
diff --git a/tensorflow/core/example/testdata/parse_example_graph_def.pbtxt b/tensorflow/core/example/testdata/parse_example_graph_def.pbtxt
new file mode 100644
index 0000000000..437499e73d
--- /dev/null
+++ b/tensorflow/core/example/testdata/parse_example_graph_def.pbtxt
@@ -0,0 +1,334 @@
+node {
+ name: "batch_constant"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_STRING
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_STRING
+ tensor_shape {
+ }
+ string_val: "z"
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/Reshape/shape"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_FLOAT
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_FLOAT
+ tensor_shape {
+ dim {
+ size: 1
+ }
+ }
+ float_val: 1
+ }
+ }
+ }
+}
+
+node {
+ name: "ParseExample/key_y"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_FLOAT
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_FLOAT
+ tensor_shape {
+ }
+ float_val: 0.0
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/Reshape_1/shape"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_FLOAT
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_FLOAT
+ tensor_shape {
+ dim {
+ size: 1
+ }
+ }
+ float_val: 1
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/key_z"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_FLOAT
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_FLOAT
+ tensor_shape {
+ }
+ float_val: 0.0
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/Reshape_2/shape"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_FLOAT
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_FLOAT
+ tensor_shape {
+ dim {
+ size: 1
+ }
+ }
+ float_val: 1
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/ParseExample/names"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_STRING
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_STRING
+ tensor_shape {
+ dim {
+ }
+ }
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/ParseExample/sparse_keys_0"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_STRING
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_STRING
+ tensor_shape {
+ }
+ string_val: "sf0"
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/ParseExample/sparse_keys_1"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_STRING
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_STRING
+ tensor_shape {
+ }
+ string_val: "sf1"
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/ParseExample/dense_keys_0"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_STRING
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_STRING
+ tensor_shape {
+ }
+ string_val: "x"
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/ParseExample/dense_keys_1"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_STRING
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_STRING
+ tensor_shape {
+ }
+ string_val: "y"
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/ParseExample/dense_keys_2"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_STRING
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_STRING
+ tensor_shape {
+ }
+ string_val: "z"
+ }
+ }
+ }
+}
+node {
+ name: "ParseExample/ParseExample"
+ op: "ParseExample"
+ input: "batch_constant"
+ input: "ParseExample/ParseExample/names"
+ input: "ParseExample/ParseExample/sparse_keys_0"
+ input: "ParseExample/ParseExample/sparse_keys_1"
+ input: "ParseExample/ParseExample/dense_keys_0"
+ input: "ParseExample/ParseExample/dense_keys_1"
+ input: "ParseExample/ParseExample/dense_keys_2"
+ input: "ParseExample/Reshape/shape"
+ input: "ParseExample/Reshape_1/shape"
+ input: "ParseExample/Reshape_2/shape"
+ attr {
+ key: "Nsparse"
+ value {
+ i: 2
+ }
+ }
+ attr {
+ key: "Ndense"
+ value {
+ i: 3
+ }
+ }
+ attr {
+ key: "sparse_types"
+ value {
+ list {
+ type: DT_STRING
+ type: DT_STRING
+ }
+ }
+ }
+ attr {
+ key: "Tdense"
+ value {
+ list {
+ type: DT_FLOAT
+ type: DT_FLOAT
+ type: DT_FLOAT
+ }
+ }
+ }
+ attr {
+ key: "dense_shapes"
+ value {
+ list {
+ shape {
+ dim {
+ size: 1
+ }
+ }
+ shape {
+ dim {
+ size: 1
+ }
+ }
+ shape {
+ dim {
+ size: 1
+ }
+ }
+ }
+ }
+ }
+}
+versions {
+ producer: 9
+}
+