Так с чего мне начать? Помимо некоторых логических ошибок c (например, в строке 43, при настройке last_sample_index
), основная проблема заключалась в том, что обратное распространение было смешано между последовательностями.
Значение: каждая последовательность смешивала значения ошибок из других последовательностей. Таким образом, даже если вход поступает из скрытого состояния, он не должен влиять на градиент других последовательностей.
Это я осознал, когда плакал поверх своей стопки бумаги, заставляя меня проверять технику BPTT (а также мои жизненные выборы) до костей, сверяя ее с помощью алгоритма обратного распространения, потому что различных последовательностей во времени - это в основном особый вид обратного распространения, где некоторые коэффициенты формул повторяются .
Учитывая это, я переработал код в разделите вычисления градиента в последовательностях.
Затем возникает проблема исчезающие / взрывающиеся градиенты . После вышеперечисленных переделок сеть все еще не сошлась из-за этого. После третьего пробоя и некоторых экспериментов, которые я обнаружил, просто наполовину уменьшить градиент, полученный из смещения второй последовательности, решает проблему исчезновения. Градиент отклонения смещен, потому что численно это самый большой из всех весов.
Теперь приведенная ниже программа работает с успешно сходящейся сетью.
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int main(int argc, char *argv[]){
srand(time(nullptr));
/* Manual BPTT with one custom implemented Neuron */
double sequence_size = 3;
double number_of_samples = 3; /* Binary addition dataset */
double minibatch_size = 4;
vector<vector<double>> net_inputs_train = { /* 2 inputs in each step */
{1,1}, {0,0}, {0,0}, /* 100 + 100 = 110 */
{1,0}, {0,1}, {1,0}, /* 101 + 010 = 111*/
{1,0}, {1,1}, {0,0}, /* 110 + 010 = 111 */
};
vector<vector<double>> expected_output = { /* 1 output in each step */
{1}, {1}, {0}, /* 110 */
{1}, {1}, {1}, /* 111 */
{1}, {1}, {1}, /* 111 */
};
double w1 = 0.5;
double w2 = 0.5;
double w3 = 0.5;
double b = 0.0;
double gradw1; /* gradients for the weights */
double gradw2;
double gradw3;
double gradb;
vector<double> neuron_data(3,0);
double neuron_deriv = 0; /* Neuron error value ( partial based on the expected output and the error function )*/
vector<double> dw1(3,0); /* derivatives for weights for each sequence */
vector<double> dw2(3,0);
vector<double> dw3(3,0);
vector<double> derb(3,0);
int first_sample_index;
double manual_error = 1.0;
double learning_rate = 1e-2;
while(manual_error > learning_rate){
for(int mbIter = 0; mbIter < minibatch_size; ++mbIter){ /* minibatches */
first_sample_index = sequence_size * (rand()%(static_cast<int>(number_of_samples)));
gradw1 = 0;
gradw2 = 0;
gradw3 = 0;
gradb = 0;
/* Fill in the data and derviatives */
neuron_data[0] = (
net_inputs_train[first_sample_index][0] * w1
+ net_inputs_train[first_sample_index][1] * w2
+ b
);
dw1[0] = net_inputs_train[first_sample_index][0];
dw2[0] = net_inputs_train[first_sample_index][1];
dw3[0] = 0;
derb[0] = 1;
neuron_data[1] = (
net_inputs_train[first_sample_index + 1][0] * w1
+ net_inputs_train[first_sample_index + 1][1] * w2
+ neuron_data[0] * w3
+ b
);
dw1[1] = net_inputs_train[first_sample_index + 1][0] + w3 * dw1[0];
dw2[1] = net_inputs_train[first_sample_index + 1][1] + w3 * dw2[0];
dw3[1] = neuron_data[0] + w3 * dw3[0];
derb[1] = 1 + derb[0] * w3;
neuron_data[2] = (
net_inputs_train[first_sample_index + 2][0] * w1
+ net_inputs_train[first_sample_index + 2][1] * w2
+ neuron_data[1] * w3
+ b
);
dw1[2] = net_inputs_train[first_sample_index + 2][0] + w3 * dw1[1];
dw2[2] = net_inputs_train[first_sample_index + 2][1] + w3 * dw2[1];
dw3[2] = neuron_data[1] + w3 * dw3[1];
derb[2] = 1 + derb[1] * w3;
/* Calculate the error and the gradients */
manual_error = (
pow((neuron_data[2] - expected_output[first_sample_index + 2][0]),2)/2.0
+pow((neuron_data[1] - expected_output[first_sample_index + 1][0]),2)/2.0
+pow((neuron_data[0] - expected_output[first_sample_index + 0][0]),2)/2.0
);
/* Calculate gradients for sequence 2 */
neuron_deriv = (
-(neuron_data[2] - expected_output[first_sample_index + 2][0])
-w3*(neuron_data[2] - expected_output[first_sample_index + 2][0])
-w3*(neuron_data[2] - expected_output[first_sample_index + 2][0])
);
gradw1 += dw1[2] * neuron_deriv;
gradw2 += dw2[2] * neuron_deriv;
gradw3 += dw3[2] * neuron_deriv;
gradb += derb[2] * neuron_deriv / 2.0;
/* Calculate gradients for sequence 1 */
neuron_deriv = (
-(neuron_data[1] - expected_output[first_sample_index + 1][0])
-w3*(neuron_data[1] - expected_output[first_sample_index + 1][0])
);
gradw1 += dw1[1] * neuron_deriv;
gradw2 += dw2[1] * neuron_deriv;
gradw3 += dw3[1] * neuron_deriv;
gradb += derb[1] * neuron_deriv;
/* Calculate gradients for sequence 0 */
neuron_deriv = -(neuron_data[0] - expected_output[first_sample_index + 0][0]);
gradw1 += dw1[0] * neuron_deriv;
gradw2 += dw2[0] * neuron_deriv;
gradw3 += dw3[0] * neuron_deriv;
gradb += derb[0] * neuron_deriv;
w1 += (learning_rate * (gradw1) / (sequence_size * minibatch_size));
w2 += (learning_rate * (gradw2) / (sequence_size * minibatch_size));
w3 += (learning_rate * (gradw3) / (sequence_size * minibatch_size));
b += (learning_rate * (gradb) / (sequence_size * minibatch_size));
std::cout << "\r Error: " << manual_error << " ";
}
}
std::cout << std::endl;
return 0;
}
Честно говоря, мне трудно поверить, что это действительно работает. Я действительно надеюсь, что смогу помочь любому, кто попробует это в будущем.