-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTensor.h
135 lines (112 loc) · 4.17 KB
/
Tensor.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#ifndef LIBDL_TENSOR_H
#define LIBDL_TENSOR_H
#define EIGEN_USE_THREADS
#include <iostream>
#include <memory>
#include <unsupported/Eigen/CXX11/Tensor>
#include <queue>
#include <optional>
#include "ops/CNode.h"
#include "ops/Leaf.h"
#include "GlobalThreadPool.h"
/*
* This class represents a tensor, including autograd abilities.
* I.e. it has a reference to its gradient, its corresponding computational node and a flag if it requires a gradient.
*
* the function backward() computes the gradient of all predecessors to this tensor
*
* The data Eigen::Tensor as well as the gradient are wrapped into shared pointers since they are also referenced from the CNode class.
*
* D and R are the template parameter for Eigen::Tensor
* */
template <typename D,std::int64_t R>
class Tensor {
public:
std::shared_ptr<Eigen::Tensor<D, R>> data;
std::shared_ptr<Eigen::Tensor<D, R>> grad;
std::optional<std::shared_ptr<CNode<D, R>>> gradFn;
bool requiresGrad;
template<typename OtherDerived>
Tensor(const OtherDerived &t, const std::array<std::int64_t, R> &shape)
: data(std::make_shared<Eigen::Tensor<D, R>>(shape)),
gradFn(std::nullopt),
requiresGrad(false) {
data->device(GlobalThreadPool::myDevice) = t;
}
Tensor(Eigen::Tensor<D, R> t, bool requiresGrad)
: data(std::make_shared<Eigen::Tensor<D, R>>(std::move(t))),
gradFn(std::nullopt),
requiresGrad(requiresGrad) {}
explicit Tensor(const std::array<std::int64_t, R> &shape, bool requiresGrad = false)
: data(std::make_shared<Eigen::Tensor<D, R>>(shape)),
gradFn(std::nullopt), // TODO leaf node
requiresGrad(requiresGrad) {}
void setGradFn(const std::shared_ptr<CNode<D, R>>& g) {
if (!CNodeBase::noGrad)
gradFn = std::optional<std::shared_ptr<CNode<D, R>>>(g);
}
bool needsGradient() {
return requiresGrad || gradFn.has_value();
}
void zeroGrad() {
grad = std::shared_ptr<Eigen::Tensor<D, R>>(nullptr);
}
// subtract this tensors gradient from this tensor (used for gradient decent)
void subGrad(D lr) {
if (grad.use_count() > 0)
data->device(GlobalThreadPool::myDevice) -= grad->constant(lr) * *grad;
}
// add the given tensor onto this tensors gradient (used for backpropagation)
void addGrad(const std::shared_ptr<Eigen::Tensor<D, R>> &g) {
if (grad.use_count() == 0) {
grad = g;
} else if (grad != g)
grad->device(GlobalThreadPool::myDevice) += *g;
}
// backpropagation algorithm
// compute the gradient of all predecessors w.r.t tensor
void backward(D v = 1) {
// neither this tensor nor any of its predecessors needs a gradient
if (!gradFn.has_value())
return;
// initial gradient
gradFn.value()->addGrad(data->constant(v));
auto head = gradFn.value();
// first go through the graph to count how many children each node has
// so that each node only computes the gradients once
std::queue<std::shared_ptr<CNodeBase>> q;
q.push(head);
while (!q.empty()) {
auto e = q.front();
q.pop();
for (const auto& n : e->parents) {
n->childrenThatNeedToComputeGradients++;
if (!n->visited) {
n->visited = true;
q.push(n);
}
}
}
// backpropagate gradients
q.push(head);
while (!q.empty()) {
auto e = q.front();
q.pop();
// if at least one children did not compute its gradients
if (e->childrenThatNeedToComputeGradients > 0) {
q.push(e);
continue;
}
// compute gradients for parents
e->computeGradients();
for (const auto& n : e->parents) {
n->childrenThatNeedToComputeGradients--;
if (n->visited) {
n->visited = false;
q.push(n);
}
}
}
}
};
#endif //LIBDL_TENSOR_H