-
Notifications
You must be signed in to change notification settings - Fork 0
/
Mlp.hpp
170 lines (143 loc) · 6.7 KB
/
Mlp.hpp
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//
// Created by paul on 25.05.18.
//
#ifndef MLPTEST_MLP_HPP
#define MLPTEST_MLP_HPP
#include <cassert>
#include <utility>
#include "Layer.hpp"
#include "Functions.hpp"
namespace ml {
namespace helper {
template<unsigned int index, unsigned int... remPack>
struct getVal;
template<unsigned int index, unsigned int In, unsigned int... remPack>
struct getVal<index, In, remPack...> {
static const unsigned int val = getVal<index - 1, remPack...>::val;
};
template<unsigned int In, unsigned int...remPack>
struct getVal<1, In, remPack...> {
static const unsigned int val = In;
};
template<unsigned int...remPack>
struct getLast {
static const unsigned int val = getVal<sizeof...(remPack), remPack...>::val;
};
}
template<int INPUT, int OUTPUT, int ... FOLLOWING_LAYERS>
class Mlp {
public:
static constexpr auto LAST_OUTPUT = helper::getLast<FOLLOWING_LAYERS...>::val;
using TransferF = std::function<double(double)>;
using CostF = std::function<double(std::array<double, LAST_OUTPUT>, std::array<double, LAST_OUTPUT>)>;
Mlp() = default;
explicit Mlp(const functions::TransferFunction &transferFunction)
: followingMlp(transferFunction), layer{},
transferFunction{transferFunction} {}
template <typename ... F>
Mlp(functions::TransferFunction transferFunction, const functions::TransferFunction& f0, F... f)
: followingMlp(f0, f...), layer{},
transferFunction{std::move(transferFunction)} {}
auto forward(const std::array<double, INPUT> &x) const -> std::array<double, LAST_OUTPUT> {
return followingMlp.forward(layer.forward(x, transferFunction));
}
auto train(const std::vector<std::array<double, INPUT>> &inputs,
const std::vector<std::array<double, LAST_OUTPUT>> &outputs, double maxError,
const CostF &costF, double learnRate,
const std::optional<std::function<void(double)>> &errorCallback = std::nullopt) {
assert(inputs.size() == outputs.size());
double error;
do {
for (std::size_t c = 0; c < inputs.size(); c++) {
adapt(inputs[c], outputs[c], learnRate);
}
error = 0.0;
for (std::size_t c = 0; c < inputs.size(); c++) {
auto mlpOutput = forward(inputs[c]);
error += costF(mlpOutput, outputs[c]);
}
if (errorCallback.has_value()) {
errorCallback.value()(error);
}
} while (error > maxError);
return error;
}
auto adapt(const std::array<double, INPUT> &input,
const std::array<double, LAST_OUTPUT> &trainerOutput,
double learnRate) -> std::array<double, INPUT> {
const auto &output = layer.forward(input, transferFunction);
auto outputError = followingMlp.adapt(output, trainerOutput, learnRate);
auto inputError = layer.backPropagate(outputError, transferFunction.getDerivative());
layer.adaptWeights(outputError, input, learnRate);
return inputError;
}
private:
Mlp<OUTPUT, FOLLOWING_LAYERS...> followingMlp;
Layer <INPUT, OUTPUT> layer;
functions::TransferFunction transferFunction;
public:
friend void to_json(nlohmann::json& j, const Mlp<INPUT, OUTPUT, FOLLOWING_LAYERS...> &mlp) {
nlohmann::json layerJson;
layerJson["layer"] = mlp.layer;
layerJson["transferFunction"] = mlp.transferFunction.getId();
j["layers"].emplace_back(layerJson);
to_json(j, mlp.followingMlp); // Yes CLion is unhappy here, it doesn't understand the recursive template
}
friend void from_json(const nlohmann::json& j, Mlp<INPUT, OUTPUT, FOLLOWING_LAYERS...> &mlp) {
assert(!j.at("layers").empty());
auto it = j.at("layers").begin();
mlp.layer = it->at("layer").get<Layer<INPUT,OUTPUT>>();
nlohmann::json newJson;
newJson["layers"] = nlohmann::json::array();
for (++it; it != j.at("layers").end(); ++it) {
newJson["layers"].emplace_back(*it);
}
mlp.transferFunction = functions::TransferFunction::functions.at(
j.at("layers").begin()->at("transferFunction").get<std::string>());
mlp.followingMlp = newJson.get<Mlp<OUTPUT, FOLLOWING_LAYERS...>>();
}
};
template<int INPUT, int OUTPUT>
class Mlp<INPUT, OUTPUT> {
public:
static constexpr auto LAST_OUTPUT = OUTPUT;
using TransferF = std::function<double(double)>;
using CostF = std::function<double(std::array<double, LAST_OUTPUT>, std::array<double, LAST_OUTPUT>)>;
Mlp() = default;
explicit Mlp(functions::TransferFunction transferFunction)
: layer{}, transferFunction{std::move(transferFunction)} {}
auto forward(std::array<double, INPUT> x) const -> std::array<double, OUTPUT> {
return layer.forward(x, transferFunction);
}
auto
adapt(std::array<double, INPUT> input, std::array<double, OUTPUT> trainerOutput,
double learnRate) -> std::array<double, INPUT> {
auto mlpOutput = layer.forward(input, transferFunction);
std::array<double, OUTPUT> outputErrror;
for (auto c = 0; c < OUTPUT; c++) {
outputErrror[c] = trainerOutput[c] - mlpOutput[c];
}
auto inputError = layer.backPropagate(outputErrror, transferFunction.getDerivative());
layer.adaptWeights(outputErrror, input, learnRate);
return inputError;
}
private:
Layer <INPUT, OUTPUT> layer;
functions::TransferFunction transferFunction;
public:
friend void to_json(nlohmann::json& j, const Mlp<INPUT, OUTPUT> &mlp) {
nlohmann::json layerJson;
layerJson["layer"] = mlp.layer;
layerJson["transferFunction"] = mlp.transferFunction.getId();
j["layers"].emplace_back(layerJson);
}
friend void from_json(const nlohmann::json& j, Mlp<INPUT, OUTPUT> &mlp) {
assert(j.at("layers").size() == 1);
auto it = j.at("layers").begin();
mlp.layer = it->at("layer").get<Layer<INPUT,OUTPUT>>();
mlp.transferFunction = functions::TransferFunction::functions.at(
it->at("transferFunction").get<std::string>());
}
};
}
#endif //MLPTEST_MLP_HPP