aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/cc/framework
diff options
context:
space:
mode:
authorGravatar Suharsh Sivakumar <suharshs@google.com>2017-04-13 11:42:52 -0800
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2017-04-13 13:05:08 -0700
commit908d5b6ede6ae829dff138a873eec397ef434cd6 (patch)
treede7898ec319637d2f6d4a78067715bd02808fb02 /tensorflow/cc/framework
parent59ccf014e89ff625dc3d9779e1fb54a980c4b6ac (diff)
Add C++ gradients to c_api.
#6268 This CL does the following: (1) Adds TF_AddGradients function to C_API which adds gradient nodes for the specified inputs. (2) Adds internal constructor for Scope, need to create a scope from an existing graph in the c_api. (3) Adds constructor for AddSymbolicGradients that assumes OnesLike when grad_inputs aren't provided. (4) Improves error message when gradients aren't provided. Change: 153092774
Diffstat (limited to 'tensorflow/cc/framework')
-rw-r--r--tensorflow/cc/framework/grad_op_registry.cc8
-rw-r--r--tensorflow/cc/framework/gradients.cc13
-rw-r--r--tensorflow/cc/framework/gradients.h11
-rw-r--r--tensorflow/cc/framework/gradients_test.cc28
-rw-r--r--tensorflow/cc/framework/scope.cc59
-rw-r--r--tensorflow/cc/framework/scope.h1
-rw-r--r--tensorflow/cc/framework/scope_internal.h33
7 files changed, 137 insertions, 16 deletions
diff --git a/tensorflow/cc/framework/grad_op_registry.cc b/tensorflow/cc/framework/grad_op_registry.cc
index 0d6a377b50..254705736e 100644
--- a/tensorflow/cc/framework/grad_op_registry.cc
+++ b/tensorflow/cc/framework/grad_op_registry.cc
@@ -32,7 +32,13 @@ bool GradOpRegistry::Register(const string& op, GradFunc func) {
Status GradOpRegistry::Lookup(const string& op, GradFunc* func) const {
auto iter = registry_.find(op);
if (iter == registry_.end()) {
- return errors::NotFound("No gradient defined for op: ", op);
+ const string error_msg =
+ "No gradient defined for op: " + op +
+ ". Please see "
+ "https://www.tensorflow.org/code/"
+ "tensorflow/cc/gradients/README.md"
+ " for instructions on how to add C++ gradients.";
+ return errors::NotFound(error_msg);
}
*func = iter->second;
return Status::OK();
diff --git a/tensorflow/cc/framework/gradients.cc b/tensorflow/cc/framework/gradients.cc
index 2c60f947a5..4ada9351ca 100644
--- a/tensorflow/cc/framework/gradients.cc
+++ b/tensorflow/cc/framework/gradients.cc
@@ -367,6 +367,19 @@ Status AddSymbolicGradients(const Scope& scope,
return builder.AddGradients();
}
+Status AddSymbolicGradients(const Scope& scope,
+ const std::vector<Output>& outputs,
+ const std::vector<Output>& inputs,
+ std::vector<Output>* grad_outputs) {
+ std::vector<Output> grad_inputs;
+ grad_inputs.reserve(outputs.size());
+ for (const Output& output : outputs) {
+ grad_inputs.emplace_back(ops::OnesLike(scope, output));
+ }
+ return AddSymbolicGradients(scope, outputs, inputs, grad_inputs,
+ grad_outputs);
+}
+
Output NoGradient() { return SymbolicGradientBuilder::NoGradient(); }
} // end namespace tensorflow
diff --git a/tensorflow/cc/framework/gradients.h b/tensorflow/cc/framework/gradients.h
index d076bc43b4..717f6f0636 100644
--- a/tensorflow/cc/framework/gradients.h
+++ b/tensorflow/cc/framework/gradients.h
@@ -27,16 +27,19 @@ namespace tensorflow {
/// derivatives of some loss function 'L' w.r.t 'outputs'), adds gradient nodes
/// to the graph associated with 'scope', which compute (and return in
/// 'grad_outputs') the symbolic partial derivatives of 'L' w.r.t 'inputs'.
-///
-
-// TODO(andydavis) Add overload of this function with no 'grad_inputs' arg.
-// Implementation will fill in 'OnesLike' for all shapes in 'outputs'.
Status AddSymbolicGradients(const Scope& scope,
const std::vector<Output>& outputs,
const std::vector<Output>& inputs,
const std::vector<Output>& grad_inputs,
std::vector<Output>* grad_outputs);
+// Same as above, but uses 'OnesLike' for all shapes in
+// 'outputs' as grad_inputs.
+Status AddSymbolicGradients(const Scope& scope,
+ const std::vector<Output>& outputs,
+ const std::vector<Output>& inputs,
+ std::vector<Output>* grad_outputs);
+
/// Returns a sentinel Output that represents 'no gradient' (i.e. no gradient
/// flows along some graph edge during backpropagation).
/// Can be returned in 'grad_outputs' by an invocation of 'AddSymbolicGradients'
diff --git a/tensorflow/cc/framework/gradients_test.cc b/tensorflow/cc/framework/gradients_test.cc
index 6c2c2fcd1e..7783bdce3a 100644
--- a/tensorflow/cc/framework/gradients_test.cc
+++ b/tensorflow/cc/framework/gradients_test.cc
@@ -40,7 +40,7 @@ class GradientsTest : public ::testing::Test {
TF_ASSERT_OK(scope_test_.ToGraphDef(&gdef_test));
GraphDef gdef_exp;
TF_ASSERT_OK(scope_expected_.ToGraphDef(&gdef_exp));
- TF_EXPECT_GRAPH_EQ(gdef_test, gdef_exp);
+ TF_EXPECT_GRAPH_EQ(gdef_exp, gdef_test);
}
Scope scope_expected_;
@@ -98,6 +98,32 @@ TEST_F(GradientsTest, OneMatMul) {
CompareTestAndExpectedGraphs();
}
+TEST_F(GradientsTest, OneMatMul_InferGradInputs) {
+ for (const bool expected : {false, true}) {
+ const Scope& scope = expected ? scope_expected_ : scope_test_;
+ // Construct forward graph.
+ auto x = Const(scope, {{1.0, 2.0}, {3.0, 4.0}});
+ auto y = Const(scope, {{1.0, 0.0}, {0.0, 1.0}});
+ auto z = MatMul(scope, x, y);
+ TF_ASSERT_OK(scope.status());
+ CHECK_NOTNULL(z.node());
+
+ if (expected) {
+ // Construct backward graph.
+ // The gradients function adds a OnesLike to create a dz of ones with the
+ // shape of z.
+ auto dz = OnesLike(scope, z);
+ auto dx = MatMul(scope, dz, y, MatMul::TransposeB(true));
+ auto dy = MatMul(scope, x, dz, MatMul::TransposeA(true));
+ } else {
+ // Call AddSymbolicGradients.
+ std::vector<Output> grad_outputs;
+ TF_ASSERT_OK(AddSymbolicGradients(scope, {z}, {x, y}, &grad_outputs));
+ }
+ }
+ CompareTestAndExpectedGraphs();
+}
+
TEST_F(GradientsTest, TwoMatMuls_Chained) {
for (const bool expected : {false, true}) {
const Scope& scope = expected ? scope_expected_ : scope_test_;
diff --git a/tensorflow/cc/framework/scope.cc b/tensorflow/cc/framework/scope.cc
index 571c6e1e57..8b7fc1406f 100644
--- a/tensorflow/cc/framework/scope.cc
+++ b/tensorflow/cc/framework/scope.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include <algorithm>
#include <vector>
-#include "tensorflow/cc/framework/scope.h"
+#include "tensorflow/cc/framework/scope_internal.h"
#include "tensorflow/core/common_runtime/shape_refiner.h"
#include "tensorflow/core/framework/node_def_util.h"
#include "tensorflow/core/graph/graph_constructor.h"
@@ -25,6 +25,20 @@ limitations under the License.
namespace tensorflow {
class Scope::Impl {
+ public:
+ // A NameMap is used to keep track of suffixes for names used in a scope. A
+ // name that has not been used so far in a scope will get no suffix. Later
+ // uses of the same name will get suffixes _1, _2, _3, etc. Multiple scopes
+ // can share the same NameMap. For instance, a new scope created using
+ // WithControlDependencies() should would share the same NameMap with the
+ // parent.
+ typedef std::unordered_map<string, int> NameMap;
+
+ Impl(const std::shared_ptr<Graph>& graph,
+ const std::shared_ptr<Status>& status,
+ const std::shared_ptr<NameMap>& name_map,
+ const std::shared_ptr<ShapeRefiner>& refiner);
+
private:
friend class Scope;
@@ -40,14 +54,6 @@ class Scope::Impl {
enum class Colocate;
};
- // A NameMap is used to keep track of suffixes for names used in a scope. A
- // name that has not been used so far in a scope will get no suffix. Later
- // uses of the same name will get suffixes _1, _2, _3, etc. Multiple scopes
- // can share the same NameMap. For instance, a new scope created using
- // WithControlDependencies() should would share the same NameMap with the
- // parent.
- typedef std::unordered_map<string, int> NameMap;
-
Impl(Graph* graph, Status* status, NameMap* name_map, ShapeRefiner* refiner);
Impl(const Scope& other, Tags::ScopeName, const string& name,
bool copy_names);
@@ -116,6 +122,17 @@ Scope::Impl::Impl(Graph* graph, Status* status, NameMap* name_map,
scope_used_(nullptr),
colocation_constraints_() {}
+Scope::Impl::Impl(const std::shared_ptr<Graph>& graph,
+ const std::shared_ptr<Status>& status,
+ const std::shared_ptr<NameMap>& name_map,
+ const std::shared_ptr<ShapeRefiner>& refiner)
+ : graph_(graph),
+ status_(status),
+ name_map_(name_map),
+ refiner_(refiner),
+ scope_used_(nullptr),
+ colocation_constraints_() {}
+
Scope Scope::NewRootScope() {
Graph* graph = new Graph(OpRegistry::Global());
ShapeRefiner* refiner =
@@ -277,7 +294,7 @@ std::shared_ptr<Graph> Scope::graph_as_shared_ptr() const {
return impl()->graph_;
}
-Status Scope::status() const { return *impl()->status_; };
+Status Scope::status() const { return *impl()->status_; }
const std::vector<Operation>& Scope::control_deps() const {
return impl()->control_deps_;
@@ -464,4 +481,26 @@ CompositeOpScopes Scope::GetCompositeOpScopes(
}
}
+class InternalScope {
+ public:
+ // NewScope doesn't take ownership of the inputs.
+ static Scope NewScope(Graph* graph, Status* status, ShapeRefiner* refiner) {
+ Scope::Impl::NameMap* name_map = new Scope::Impl::NameMap;
+ for (const Node* node : graph->nodes()) {
+ (*name_map)[node->name()] = 0;
+ }
+ // We provide null destructors for these shared ptrs (except for name_map)
+ // since the caller owns them and doesn't want the scope to destroy them.
+ return Scope(new Scope::Impl(
+ std::shared_ptr<Graph>(graph, [](Graph*) {}),
+ std::shared_ptr<Status>(status, [](Status*) {}),
+ std::shared_ptr<Scope::Impl::NameMap>(name_map),
+ std::shared_ptr<ShapeRefiner>(refiner, [](ShapeRefiner*) {})));
+ }
+};
+
+Scope NewInternalScope(Graph* graph, Status* status, ShapeRefiner* refiner) {
+ return InternalScope::NewScope(graph, status, refiner);
+}
+
} // namespace tensorflow
diff --git a/tensorflow/cc/framework/scope.h b/tensorflow/cc/framework/scope.h
index ce70da7096..ec3543772d 100644
--- a/tensorflow/cc/framework/scope.h
+++ b/tensorflow/cc/framework/scope.h
@@ -204,6 +204,7 @@ class Scope {
const std::vector<Operation>& control_deps() const;
private:
+ friend class InternalScope;
class Impl;
std::unique_ptr<Impl> impl_;
Impl* impl() { return impl_.get(); }
diff --git a/tensorflow/cc/framework/scope_internal.h b/tensorflow/cc/framework/scope_internal.h
new file mode 100644
index 0000000000..f2a911877f
--- /dev/null
+++ b/tensorflow/cc/framework/scope_internal.h
@@ -0,0 +1,33 @@
+/* Copyright 2016 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.
+==============================================================================*/
+
+#ifndef THIRD_PARTY_TENSORFLOW_CC_FRAMEWORK_SCOPE_INTERNAL_H_
+#define THIRD_PARTY_TENSORFLOW_CC_FRAMEWORK_SCOPE_INTERNAL_H_
+
+#include "tensorflow/cc/framework/scope.h"
+
+namespace tensorflow {
+
+class ShapeRefiner;
+
+// NewInternalScope returns a new scope which doesn't take ownership of
+// graph, status, name_map, and refiner.
+// This is intended to enable the C API (which are used by other language
+// bindings) to create a Scope and access C++ functionality (i.e. gradients).
+Scope NewInternalScope(Graph* graph, Status* status, ShapeRefiner* refiner);
+
+} // namespace tensorflow
+
+#endif // THIRD_PARTY_TENSORFLOW_CC_FRAMEWORK_SCOPE_INTERNAL_H_