• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2015 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 
16 #include <deque>
17 #include <vector>
18 
19 #include "tensorflow/cc/framework/grad_op_registry.h"
20 #include "tensorflow/cc/framework/gradients.h"
21 #include "tensorflow/cc/framework/while_gradients.h"
22 #include "tensorflow/cc/ops/standard_ops.h"
23 #include "tensorflow/core/framework/function.h"
24 #include "tensorflow/core/framework/node_def_util.h"
25 #include "tensorflow/core/framework/op.h"
26 #include "tensorflow/core/framework/op_kernel.h"
27 #include "tensorflow/core/graph/algorithm.h"
28 #include "tensorflow/core/graph/graph_constructor.h"
29 #include "tensorflow/core/graph/while_context.h"
30 #include "tensorflow/core/lib/gtl/map_util.h"
31 #include "tensorflow/core/platform/macros.h"
32 
33 namespace tensorflow {
34 namespace {
35 
36 struct OutputHash {
operator ()tensorflow::__anon2882cf250111::OutputHash37   uint64 operator()(const Output& x) const {
38     return x.hash();
39   }
40 };
41 
42 struct OutputEq {
operator ()tensorflow::__anon2882cf250111::OutputEq43   bool operator()(const Output& x, const Output& y) const {
44     return (x.node() == y.node()) && (x.index() == y.index());
45   }
46 };
47 
48 class SymbolicGradientBuilder {
49  public:
50   SymbolicGradientBuilder(const Scope& scope,
51                           const ops::GradOpRegistry* registry,
52                           const std::vector<Output>& outputs,
53                           const std::vector<Output>& inputs,
54                           const std::vector<Output>& grad_inputs,
55                           std::vector<Output>* grad_outputs);
56 
57   Status AddGradients();
58 
NoGradient()59   static Output NoGradient() { return Output(nullptr, -1); }
60 
61  private:
62   Status Initialize();
63 
64   // For each forward edge from `src` to `dst` in the initial/forward graph:
65   // propagates gradients `dst_grad` backwards along the edge from `src`
66   // to `dst` in the graph. This will add `dst_grad` to the list of pending
67   // gradients for the node associated with `src`.
68   Status BackpropAlongEdge(const Output& dst_grad, const Output& src);
69 
70   // Adds a node to the graph (returned in `grad`) that sums the in-bound
71   // gradients to `src` (if there are more than one).
72   Status SumGradients(const Output& src, Output* grad);
73 
74   // Returns true if `opname` is registered in `registry_` with no gradient
75   // function, false otherwise.
76   bool IsPrimitiveOpWithNoGrad(const string& opname);
77 
78   // Call the gradient function for `op`, storing the result in `grad_outputs`.
79   Status CallGradFunction(const Operation& op,
80                           const std::vector<Output>& grad_inputs,
81                           std::vector<Output>* grad_outputs);
82 
83   // Returns a list mapping whether each node in the graph is reachable
84   // from outputs_. Keyed by node id.
85   std::vector<bool> GetReachableNodes();
86 
87   // Creates the gradient subgraph for a while loop (or just stores
88   // `summed_grads` if not all incoming gradients are available yet). All exit
89   // nodes (which are the first nodes of a loop encountered in the backwards
90   // pass) are passed to this function rather than processed normally.
91   // `summed_grads` is the sum of `exit_node`s gradients.
92   Status ProcessWhileLoop(Node* exit_node, const Output& summed_grads);
93 
94   // Gets the set of node ids at which to stop backprop. These are all elements
95   // of `outputs_` that do not get transitively consumed by other `outputs_`.
96   // Used to identify nodes at which to stop backprop.
97   std::unordered_set<int> GetStopBackpropNodes(
98       const std::vector<bool>& reachable_nodes,
99       const std::unordered_set<int>& output_nodes);
100 
101   const Scope& scope_;
102   const ops::GradOpRegistry* registry_;
103   const std::vector<Output>& outputs_;
104   const std::vector<Output>& inputs_;
105   const std::vector<Output>& grad_inputs_;
106   std::vector<Output>* grad_outputs_;
107 
108   // A vector of output endpoints which represents backpropagated gradients.
109   typedef std::vector<Output> BackproppedGradients;
110 
111   // backprops_ is a map from a node output to its accumulated
112   // gradients.  When a node output has accumulated all its
113   // gradients, we add a node which sums them up.
114   std::unordered_map<Output, BackproppedGradients, OutputHash, OutputEq>
115       backprops_;
116 
117   // pending[i] is count-down counter for i-th node's expected
118   // backprops.  When pending[i] becomes zero, we collected all
119   // backprop gradients for all outputs of the ith-node.
120   std::vector<int> pending_;
121 
122   // `ready` keeps track of nodes that have been completely
123   // backpropped. Initially, for every output in `outputs_`, we add initial
124   // gradients from `grad_inputs_`.
125   std::deque<Node*> ready_;
126 
127   // The set of node ids in `inputs_`. Used to identify nodes at backprop
128   // frontier. Maps from Output -> index into `grad_outputs_`.
129   std::unordered_map<Output, int, OutputHash, OutputEq> input_nodes_;
130 
131   // For each while loop in the graph, collects the summed gradients for each of
132   // the loop's exit nodes. Note that unlike backprops_, this map contains the
133   // output of SumGradients(), not the input (i.e. each exit node may have
134   // multiple incoming gradients, but we only store the combined Output here).
135   std::map<WhileContext*, std::map<Node*, Output>> while_backprops_;
136 
137   TF_DISALLOW_COPY_AND_ASSIGN(SymbolicGradientBuilder);
138 };
139 
SymbolicGradientBuilder(const Scope & scope,const ops::GradOpRegistry * registry,const std::vector<Output> & outputs,const std::vector<Output> & inputs,const std::vector<Output> & grad_inputs,std::vector<Output> * grad_outputs)140 SymbolicGradientBuilder::SymbolicGradientBuilder(
141     const Scope& scope, const ops::GradOpRegistry* registry,
142     const std::vector<Output>& outputs, const std::vector<Output>& inputs,
143     const std::vector<Output>& grad_inputs, std::vector<Output>* grad_outputs)
144     : scope_(scope),
145       registry_(registry),
146       outputs_(outputs),
147       inputs_(inputs),
148       grad_inputs_(grad_inputs),
149       grad_outputs_(grad_outputs) {}
150 
BackpropAlongEdge(const Output & dst_grad,const Output & src)151 Status SymbolicGradientBuilder::BackpropAlongEdge(const Output& dst_grad,
152                                                   const Output& src) {
153   if (src.node() == nullptr) {
154     return errors::Internal("Attempted to backprop along an invalid edge.");
155   }
156   auto iter = backprops_.find(src);
157   if (iter != backprops_.end()) {
158     auto* grads = &iter->second;
159     grads->push_back(dst_grad);
160     if (--pending_[src.node()->id()] == 0) {
161       ready_.push_back(src.node());
162     }
163   }
164   return Status::OK();
165 }
166 
GetReachableNodes()167 std::vector<bool> SymbolicGradientBuilder::GetReachableNodes() {
168   std::vector<bool> reachable_nodes(scope_.graph()->num_node_ids(), false);
169   std::deque<Node*> queue;
170   for (const Output& out : outputs_) {
171     if (!reachable_nodes[out.node()->id()]) {
172       queue.push_back(out.node());
173       reachable_nodes[out.node()->id()] = true;
174     }
175   }
176 
177   while (!queue.empty()) {
178     Node* n = queue.front();
179     queue.pop_front();
180     for (const Edge* e : n->in_edges()) {
181       if (e->IsControlEdge()) continue;
182       if (!reachable_nodes[e->src()->id()]) {
183         queue.push_back(e->src());
184         reachable_nodes[e->src()->id()] = true;
185       }
186     }
187   }
188   return reachable_nodes;
189 }
190 
GetStopBackpropNodes(const std::vector<bool> & reachable_nodes,const std::unordered_set<int> & output_nodes)191 std::unordered_set<int> SymbolicGradientBuilder::GetStopBackpropNodes(
192     const std::vector<bool>& reachable_nodes,
193     const std::unordered_set<int>& output_nodes) {
194   // Output nodes that get transitively consumed by other `outputs_` are stored
195   // in `internal_outputs`.
196   std::unordered_set<int> internal_outputs;
197   std::unordered_set<Node*> visited;
198   // Initialize `queue` for BFS traversal. Nodes in `queue` hold upcoming nodes
199   // along with the last Node in `output_` encountered along that path. If no
200   // `output_` node was encountered, pair.second will be nullptr.
201   std::deque<std::pair<Node*, Node*>> queue;
202   for (const Output& nout : inputs_) {
203     auto const& pair = visited.insert(nout.node());
204     if (pair.second) {
205       queue.push_back(std::make_pair(nout.node(), static_cast<Node*>(nullptr)));
206     }
207   }
208   // BFS from nodes in 'inputs_' along out edges for the entire graph. Internal
209   // output nodes are recorded during the traversal. All nodes that are output
210   // nodes but not internal output nodes are considered the frontier of the
211   // output nodes, and thus our stop backprop nodes.
212   while (!queue.empty()) {
213     std::pair<Node*, Node*> p = queue.front();
214     Node* n = p.first;
215     queue.pop_front();
216     for (const Edge* e : n->out_edges()) {
217       // If a node is not reachable from outputs_, we can stop.
218       if (e->IsControlEdge() || !reachable_nodes[e->dst()->id()]) continue;
219 
220       auto const& pair = visited.insert(e->dst());
221       if (pair.second) {
222         int node_id = e->dst()->id();
223         Node* last_output_node = p.second;
224         if (output_nodes.find(node_id) != output_nodes.end()) {
225           // We reached an output node.
226           if (last_output_node != nullptr) {
227             // If we had already found an output node on this path so we mark
228             // it as an internal output.
229             internal_outputs.insert(last_output_node->id());
230           }
231           // Mark this newly found output node to insert in the queue.
232           last_output_node = e->dst();
233         }
234         queue.push_back(std::make_pair(e->dst(), last_output_node));
235       }
236     }
237   }
238   // Finally, we set stop_backprop_nodes to all output_nodes that aren't also
239   // internal_outputs.
240   std::unordered_set<int> stop_backprop_nodes;
241   for (int output_node : output_nodes) {
242     if (internal_outputs.find(output_node) == internal_outputs.end()) {
243       stop_backprop_nodes.insert(output_node);
244     }
245   }
246   return stop_backprop_nodes;
247 }
248 
Initialize()249 Status SymbolicGradientBuilder::Initialize() {
250   if (outputs_.size() != grad_inputs_.size()) {
251     return errors::InvalidArgument(
252         "Must specify a gradient input for each output.");
253   }
254   std::vector<bool> reachable_nodes = GetReachableNodes();
255   for (const Output& input : inputs_) {
256     if (!reachable_nodes[input.node()->id()]) {
257       return errors::InvalidArgument(
258           "Cannot compute the partial derivative for node '",
259           input.node()->name(),
260           "' as it's unreachable from the output node(s).");
261     }
262   }
263   grad_outputs_->clear();
264   grad_outputs_->resize(inputs_.size());
265 
266   std::unordered_set<int> output_nodes;
267   output_nodes.reserve(outputs_.size());
268   for (size_t i = 0; i < outputs_.size(); ++i) {
269     output_nodes.insert(outputs_[i].node()->id());
270   }
271 
272   std::unordered_set<int> stop_backprop_nodes =
273       GetStopBackpropNodes(reachable_nodes, output_nodes);
274 
275   // Populate `input_nodes_` from Outputs in `inputs_`.
276   input_nodes_.reserve(inputs_.size());
277   for (size_t i = 0; i < inputs_.size(); ++i) {
278     input_nodes_.insert({inputs_[i], i});
279   }
280 
281   // TODO(andydavis) Consider a more efficient data structure for `pending_` to
282   // handle computing gradients over small subgraphs from a very large graph.
283   pending_.resize(scope_.graph()->num_node_ids(), 0);
284   {
285     backprops_.clear();
286     std::unordered_set<Node*> visited;
287     std::deque<Node*> queue;
288     for (const Output& nout : inputs_) {
289       auto const& pair = visited.insert(nout.node());
290       if (pair.second) {
291         queue.push_back(nout.node());
292       }
293     }
294 
295     // Going forward to figure out which endpoints need backprop-ed.
296     // A node's endpoints need to be backprop-ed only if one of the
297     // arg node can reach the node via data edges.
298     while (!queue.empty()) {
299       Node* n = queue.front();
300       queue.pop_front();
301       for (int i = 0; i < n->num_outputs(); ++i) {
302         backprops_[{n, i}].clear();
303       }
304       int num_expected_backprops = 0;
305       if (stop_backprop_nodes.find(n->id()) == stop_backprop_nodes.end()) {
306         // Internal node: continue BFS along connected outputs.
307         for (const Edge* e : n->out_edges()) {
308           // If a node is not reachable from outputs_,
309           // we don't expect it to receive a backpropagated gradient.
310           // It will not be counted in num_expected_backprops.
311           if (e->IsControlEdge() || !reachable_nodes[e->dst()->id()]) continue;
312           auto const& pair = visited.insert(e->dst());
313           if (pair.second) {
314             queue.push_back(e->dst());
315           }
316           ++num_expected_backprops;
317         }
318       }
319       if (output_nodes.find(n->id()) != output_nodes.end()) {
320         // Output node: update `num_expected_backprops` for each Output in
321         // `outputs_` that references `n`.
322         for (const Output& output : outputs_) {
323           if (output.node() == n) {
324             ++num_expected_backprops;
325           }
326         }
327       }
328       pending_[n->id()] = num_expected_backprops;
329     }
330   }
331 
332   {
333     // Initialize backprop with `grad_inputs_`.
334     const size_t num_dy = grad_inputs_.size();
335     for (size_t i = 0; i < num_dy; ++i) {
336       TF_RETURN_IF_ERROR(BackpropAlongEdge(grad_inputs_[i], outputs_[i]));
337     }
338   }
339   return Status::OK();
340 }
341 
SumGradients(const Output & src,Output * grad)342 Status SymbolicGradientBuilder::SumGradients(const Output& src, Output* grad) {
343   auto iter = backprops_.find(src);
344   if (iter == backprops_.end()) {
345     return errors::Internal(
346         "Unable to find backprop list for node.id ", src.node()->name());
347   }
348   const auto& grads = iter->second;
349   // Filter any backproped 'NoGradient' Outputs from 'grads' (if needed).
350   // Return any valid backproped gradients that remain after filtering,
351   // or 'NoGradient' otherwise.
352   std::vector<Output> grads_to_keep;
353   for (const Output& o : grads) {
354     if (o == NoGradient()) continue;
355     grads_to_keep.push_back(o);
356   }
357 
358   if (grads_to_keep.empty()) {
359     // Nothing propagated back. Return 'NoGradient'.
360     *grad = NoGradient();
361   } else if (grads_to_keep.size() == 1) {
362     // Just one backprop edge.
363     *grad = grads_to_keep[0];
364   } else {
365     // Otherwise, adds backprop-ed gradients.
366     // TODO(andydavis) Use a better accumulator here.
367     *grad = ops::AddN(scope_, grads_to_keep);
368   }
369 
370   return Status::OK();
371 }
372 
IsPrimitiveOpWithNoGrad(const string & opname)373 bool SymbolicGradientBuilder::IsPrimitiveOpWithNoGrad(const string& opname) {
374   ops::GradFunc grad_fn;
375   Status s = registry_->Lookup(opname, &grad_fn);
376   return s.ok() && (grad_fn == nullptr);
377 }
378 
CallGradFunction(const Operation & op,const std::vector<Output> & grad_inputs,std::vector<Output> * grad_outputs)379 Status SymbolicGradientBuilder::CallGradFunction(
380     const Operation& op,
381     const std::vector<Output>& grad_inputs,
382     std::vector<Output>* grad_outputs) {
383   ops::GradFunc grad_fn;
384   TF_RETURN_IF_ERROR(registry_->Lookup(op.node()->type_string(), &grad_fn));
385   TF_RETURN_IF_ERROR(grad_fn(scope_, op, grad_inputs, grad_outputs));
386   TF_RETURN_IF_ERROR(scope_.status());
387   return Status::OK();
388 }
389 
ProcessWhileLoop(Node * exit_node,const Output & summed_grads)390 Status SymbolicGradientBuilder::ProcessWhileLoop(Node* exit_node,
391                                                  const Output& summed_grads) {
392   // TODO(skyewm): detect second-order gradient and return bad status
393   // TODO(skyewm): handle (or at least detect) nested while loops
394 
395   // TODO(skyewm): handle NoGradient in while loop
396   if (summed_grads == NoGradient()) {
397     return errors::Unimplemented(
398         "Missing gradient into while loop not yet implemented");
399   }
400 
401   DCHECK(exit_node->IsExit());
402   WhileContext* while_ctx = exit_node->while_ctx();
403   DCHECK(while_ctx != nullptr);
404 
405   // Record 'summed_grads' as the backprop input associated with 'exit_node'
406   std::map<Node*, Output>& backprops = while_backprops_[while_ctx];
407   DCHECK(backprops.find(exit_node) == backprops.end());
408   backprops[exit_node] = summed_grads;
409 
410   // Wait until we have all exit nodes' backprops collected before processing
411   // the while loop.
412   // TODO(skyewm): what if not all the exit nodes are reachable?
413   if (backprops.size() < while_ctx->exit_nodes().size()) return Status::OK();
414 
415   // We've seen all the exit nodes for this loop and have collected all the
416   // backprops. Create the gradient graph for the while loop.
417   Scope while_scope =
418       scope_.NewSubScope(strings::StrCat(while_ctx->frame_name(), "_grad"));
419   std::vector<Output> dy;
420   for (Node* n : while_ctx->exit_nodes()) dy.push_back(backprops[n]);
421   std::vector<Output> dx;
422   TF_RETURN_IF_ERROR(AddWhileLoopGradient(while_ctx, while_scope, dy, &dx));
423 
424   // Backprop along the in edges to the while loop (i.e. the inputs to the enter
425   // nodes)
426   DCHECK_EQ(dx.size(), while_ctx->enter_nodes().size());
427   for (int i = 0; i < dx.size(); ++i) {
428     Node* enter_node = while_ctx->enter_nodes()[i];
429     for (const Edge* e : enter_node->in_edges()) {
430       if (e->IsControlEdge()) continue;
431       TF_RETURN_IF_ERROR(BackpropAlongEdge(dx[i], {e->src(), e->src_output()}));
432     }
433   }
434   return Status::OK();
435 }
436 
AddGradients()437 Status SymbolicGradientBuilder::AddGradients() {
438   // Initialize backprops.
439   TF_RETURN_IF_ERROR(Initialize());
440 
441   // Backward propagation.
442   std::vector<Output> dy;
443   while (!ready_.empty()) {
444     // n has collected all gradients.
445     Node* n = ready_.front();
446     ready_.pop_front();
447 
448     // dy[i] is the sum of i-th output's backpropped gradients.
449     const int num_y = n->num_outputs();
450     dy.clear();
451     dy.resize(num_y, {nullptr, 0});
452     std::vector<int> no_grad_dy_indices;
453     for (int i = 0; i < num_y; ++i) {
454       TF_RETURN_IF_ERROR(SumGradients({n, i}, &dy[i]));
455       if (dy[i] == NoGradient()) {
456         no_grad_dy_indices.push_back(i);
457       }
458       auto iter = input_nodes_.find({n, i});
459       if (iter != input_nodes_.end()) {
460         // Return gradients for Output in 'grad_outputs_'.
461         (*grad_outputs_)[iter->second] = dy[i];
462       }
463     }
464 
465     // Stop backprop if none of the inputs to `n` are in `backprops_'.
466     bool stop_node = true;
467     for (const Edge* e : n->in_edges()) {
468       if (e->IsControlEdge()) continue;
469       if (backprops_.find({e->src(), e->src_output()}) != backprops_.end()) {
470         stop_node = false;
471         break;
472       }
473     }
474 
475     if (stop_node) {
476       continue;
477     }
478 
479     // Special case: if we find an exit node, process the associated while loop.
480     // Note that ProcessWhileLoop() calls BackpropAlongEdge() if necessary
481     // (which updates ready_), and we skip all the regular processing below
482     // after calling it.
483     if (n->IsExit()) {
484       DCHECK_EQ(dy.size(), 1);
485       TF_RETURN_IF_ERROR(ProcessWhileLoop(n, dy[0]));
486       continue;
487     }
488     // All loop-specific control flow ops should have been handled above
489     DCHECK(!n->IsEnter() && !n->IsNextIteration()) << n->DebugString();
490 
491     const size_t num_no_grad = no_grad_dy_indices.size();
492     if (IsPrimitiveOpWithNoGrad(n->type_string()) || num_no_grad == num_y) {
493       // No grad defined for this op, or all outputs returned 'NoGradient':
494       // Backprop 'NoGradient' along the in edges.
495       for (const Edge* e : n->in_edges()) {
496         if (e->IsControlEdge()) continue;
497         TF_RETURN_IF_ERROR(
498             BackpropAlongEdge(NoGradient(), {e->src(), e->src_output()}));
499       }
500       continue;
501     }
502 
503     if (num_no_grad > 0 && num_no_grad < num_y) {
504       // The outputs of 'n' returned a mixture of valid gradients and
505       // 'NoGradient'. Therefore, we need to add 'ZerosLike' nodes for each
506       // 'NoGradient' output before we call the gradient function for 'n'.
507       // TODO(andydavis) If static shapes are known, replace 'ZerosLike' with
508       // zero-filled Constant node of appropriate shape.
509       for (const int dy_index : no_grad_dy_indices) {
510         dy[dy_index] = ops::ZerosLike(scope_, Output(n, dy_index));
511       }
512     }
513 
514     // TODO(andydavis) Add option to encapsulate grad function in
515     // SymbolicGradientOp (as opposed to inlining into the graph).
516     std::vector<Output> dx;
517     TF_RETURN_IF_ERROR(CallGradFunction(Operation(n), dy, &dx));
518 
519     // Backprop along the in edges.
520     // TODO(andydavis) Find cleaner way to map each grad output returned by
521     // gradient function to the src node/output to which it should be
522     // backproped. Maybe grad functions can return a vector of Output pairs to
523     // make this association explicit.
524     size_t dx_index = 0;
525     for (const Edge* e : n->in_edges()) {
526       if (e->IsControlEdge()) continue;
527       if (dx_index == dx.size()) {
528         return errors::Internal(
529             "Invalid gradient output index: ", dx_index, " size: ", dx.size());
530       }
531       TF_RETURN_IF_ERROR(
532           BackpropAlongEdge(dx[dx_index++], {e->src(), e->src_output()}));
533     }
534   }
535 
536   // Check if any input nodes still have pending gradients and have not been
537   // processed yet. This happens if not all outputs of a node are in 'inputs_'.
538   std::unordered_map<Node*, int> requested_grads;
539   for (const Output& nout : inputs_) {
540     if (pending_[nout.node()->id()] > 0) {
541       DCHECK_GT(nout.node()->num_outputs(), 1);
542       int idx = input_nodes_[nout];
543       DCHECK(((*grad_outputs_)[idx].node() == nullptr));
544       TF_RETURN_IF_ERROR(SumGradients(nout, &(*grad_outputs_)[idx]));
545       ++requested_grads[nout.node()];
546     }
547   }
548   for (const auto& p : requested_grads) {
549     int num_requested_inputs = p.first->num_outputs() - pending_[p.first->id()];
550     CHECK_EQ(num_requested_inputs, p.second);
551   }
552   return Status::OK();
553 }
554 
555 }  // namespace
556 
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)557 Status AddSymbolicGradients(const Scope& scope,
558                             const std::vector<Output>& outputs,
559                             const std::vector<Output>& inputs,
560                             const std::vector<Output>& grad_inputs,
561                             std::vector<Output>* grad_outputs) {
562   SymbolicGradientBuilder builder(scope, ops::GradOpRegistry::Global(), outputs,
563                                   inputs, grad_inputs, grad_outputs);
564   return builder.AddGradients();
565 }
566 
AddSymbolicGradients(const Scope & scope,const std::vector<Output> & outputs,const std::vector<Output> & inputs,std::vector<Output> * grad_outputs)567 Status AddSymbolicGradients(const Scope& scope,
568                             const std::vector<Output>& outputs,
569                             const std::vector<Output>& inputs,
570                             std::vector<Output>* grad_outputs) {
571   std::vector<Output> grad_inputs;
572   grad_inputs.reserve(outputs.size());
573   for (const Output& output : outputs) {
574     grad_inputs.emplace_back(ops::OnesLike(scope, output));
575   }
576   return AddSymbolicGradients(scope, outputs, inputs, grad_inputs,
577                               grad_outputs);
578 }
579 
NoGradient()580 Output NoGradient() { return SymbolicGradientBuilder::NoGradient(); }
581 
582 }  // end namespace tensorflow
583